From cbbca0b41e0034a2cd48c1c353ccfbd98f73cb27 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Wed, 13 Jul 2016 00:06:37 +0300 Subject: [PATCH 001/414] wip(android+core): Migration to a new Groups API --- actor-sdk/sdk-api/actor.json | 18 +- .../models/im/actor/api/scheme.mps | 12 +- .../toolbar/ChatToolbarFragment.java | 2 +- .../main/java/im/actor/core/api/ApiGroup.java | 69 +-- .../im/actor/core/entity/EntityConverter.java | 2 +- .../main/java/im/actor/core/entity/Group.java | 396 ++++++++++-------- .../modules/api/ApiSupportConfiguration.java | 1 + .../core/modules/groups/GroupsModule.java | 2 +- .../modules/groups/router/GroupRouter.java | 2 +- .../java/im/actor/core/viewmodel/GroupVM.java | 111 ++--- .../im/actor/runtime/mvvm/ValueModel.java | 8 +- 11 files changed, 296 insertions(+), 327 deletions(-) diff --git a/actor-sdk/sdk-api/actor.json b/actor-sdk/sdk-api/actor.json index 1fade09012..3cf11dee72 100644 --- a/actor-sdk/sdk-api/actor.json +++ b/actor-sdk/sdk-api/actor.json @@ -8070,7 +8070,8 @@ "childType": "bool" }, "id": 16, - "name": "isAdmin" + "name": "isAdmin", + "deprecated": "true" }, { "type": { @@ -8078,7 +8079,8 @@ "childType": "userId" }, "id": 8, - "name": "creatorUid" + "name": "creatorUid", + "deprecated": "true" }, { "type": { @@ -8089,7 +8091,8 @@ } }, "id": 9, - "name": "members" + "name": "members", + "deprecated": "true" }, { "type": { @@ -8097,7 +8100,8 @@ "childType": "date" }, "id": 10, - "name": "createDate" + "name": "createDate", + "deprecated": "true" }, { "type": { @@ -8105,7 +8109,8 @@ "childType": "string" }, "id": 17, - "name": "theme" + "name": "theme", + "deprecated": "true" }, { "type": { @@ -8113,7 +8118,8 @@ "childType": "string" }, "id": 18, - "name": "about" + "name": "about", + "deprecated": "true" } ] } diff --git a/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps b/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps index 60c935120c..cec1dde0e6 100644 --- a/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps +++ b/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps @@ -7080,7 +7080,7 @@ - + @@ -7088,7 +7088,7 @@ - + @@ -7096,7 +7096,7 @@ - + @@ -7106,7 +7106,7 @@ - + @@ -7114,7 +7114,7 @@ - + @@ -7122,7 +7122,7 @@ - + diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/toolbar/ChatToolbarFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/toolbar/ChatToolbarFragment.java index 0f51eda4fb..e93612fcf7 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/toolbar/ChatToolbarFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/toolbar/ChatToolbarFragment.java @@ -257,7 +257,7 @@ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { if (peer.getPeerType() == PeerType.PRIVATE) { callsEnabled = !users().get(peer.getPeerId()).isBot(); } else if (peer.getPeerType() == PeerType.GROUP) { - callsEnabled = groups().get(peer.getPeerId()).getMembersCount() <= MAX_USERS_FOR_CALLS; + callsEnabled = groups().get(peer.getPeerId()).getMembersCount().get() <= MAX_USERS_FOR_CALLS; videoCallsEnabled = false; } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiGroup.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiGroup.java index 788c3223e9..95963d3e5e 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiGroup.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiGroup.java @@ -26,14 +26,8 @@ public class ApiGroup extends BserObject { private ApiGroupType groupType; private Boolean canSendMessage; private ApiMapValue ext; - private Boolean isAdmin; - private int creatorUid; - private List members; - private long createDate; - private String theme; - private String about; - - public ApiGroup(int id, long accessHash, @NotNull String title, @Nullable ApiAvatar avatar, @Nullable Integer membersCount, @Nullable Boolean isMember, @Nullable Boolean isHidden, @Nullable ApiGroupType groupType, @Nullable Boolean canSendMessage, @Nullable ApiMapValue ext, @Nullable Boolean isAdmin, int creatorUid, @NotNull List members, long createDate, @Nullable String theme, @Nullable String about) { + + public ApiGroup(int id, long accessHash, @NotNull String title, @Nullable ApiAvatar avatar, @Nullable Integer membersCount, @Nullable Boolean isMember, @Nullable Boolean isHidden, @Nullable ApiGroupType groupType, @Nullable Boolean canSendMessage, @Nullable ApiMapValue ext) { this.id = id; this.accessHash = accessHash; this.title = title; @@ -44,12 +38,6 @@ public ApiGroup(int id, long accessHash, @NotNull String title, @Nullable ApiAva this.groupType = groupType; this.canSendMessage = canSendMessage; this.ext = ext; - this.isAdmin = isAdmin; - this.creatorUid = creatorUid; - this.members = members; - this.createDate = createDate; - this.theme = theme; - this.about = about; } public ApiGroup() { @@ -104,34 +92,6 @@ public ApiMapValue getExt() { return this.ext; } - @Nullable - public Boolean isAdmin() { - return this.isAdmin; - } - - public int getCreatorUid() { - return this.creatorUid; - } - - @NotNull - public List getMembers() { - return this.members; - } - - public long getCreateDate() { - return this.createDate; - } - - @Nullable - public String getTheme() { - return this.theme; - } - - @Nullable - public String getAbout() { - return this.about; - } - @Override public void parse(BserValues values) throws IOException { this.id = values.getInt(1); @@ -147,16 +107,6 @@ public void parse(BserValues values) throws IOException { } this.canSendMessage = values.optBool(26); this.ext = values.optObj(22, new ApiMapValue()); - this.isAdmin = values.optBool(16); - this.creatorUid = values.getInt(8); - List _members = new ArrayList(); - for (int i = 0; i < values.getRepeatedCount(9); i ++) { - _members.add(new ApiMember()); - } - this.members = values.getRepeatedObj(9, _members); - this.createDate = values.getLong(10); - this.theme = values.optString(17); - this.about = values.optString(18); if (values.hasRemaining()) { setUnmappedObjects(values.buildRemaining()); } @@ -191,18 +141,6 @@ public void serialize(BserWriter writer) throws IOException { if (this.ext != null) { writer.writeObject(22, this.ext); } - if (this.isAdmin != null) { - writer.writeBool(16, this.isAdmin); - } - writer.writeInt(8, this.creatorUid); - writer.writeRepeatedObj(9, this.members); - writer.writeLong(10, this.createDate); - if (this.theme != null) { - writer.writeString(17, this.theme); - } - if (this.about != null) { - writer.writeString(18, this.about); - } if (this.getUnmappedObjects() != null) { SparseArray unmapped = this.getUnmappedObjects(); for (int i = 0; i < unmapped.size(); i++) { @@ -224,9 +162,6 @@ public String toString() { res += ", groupType=" + this.groupType; res += ", canSendMessage=" + this.canSendMessage; res += ", ext=" + this.ext; - res += ", isAdmin=" + this.isAdmin; - res += ", members=" + this.members.size(); - res += ", createDate=" + this.createDate; res += "}"; return res; } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/EntityConverter.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/EntityConverter.java index 2e47e22231..14528bdfe2 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/EntityConverter.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/EntityConverter.java @@ -32,7 +32,7 @@ public static MessageState convert(ApiMessageState state) { } public static Group convert(ApiGroup group) { - return new Group(group); + return new Group(group, null); } public static PeerType convert(ApiPeerType peerType) { diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Group.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Group.java index 21f0af7884..007120f56e 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Group.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Group.java @@ -15,18 +15,24 @@ import im.actor.core.api.ApiAvatar; import im.actor.core.api.ApiGroup; +import im.actor.core.api.ApiGroupFull; import im.actor.core.api.ApiMember; import im.actor.runtime.bser.BserCreator; import im.actor.runtime.bser.BserValues; import im.actor.runtime.bser.BserWriter; import im.actor.runtime.storage.KeyValueItem; -public class Group extends WrapperEntity implements KeyValueItem { +public class Group extends WrapperExtEntity implements KeyValueItem { private static final int RECORD_ID = 10; + private static final int RECORD_EXT_ID = 11; public static BserCreator CREATOR = Group::new; + // + // Main + // + @Property("readonly, nonatomic") private int groupId; private long accessHash; @@ -38,9 +44,17 @@ public class Group extends WrapperEntity implements KeyValueItem { @Property("readonly, nonatomic") private Avatar avatar; @Property("readonly, nonatomic") - private int creatorId; - @Property("readonly, nonatomic") private boolean isHidden; + @Property("readonly, nonatomic") + private int membersCount; + @Property("readonly, nonatomic") + private boolean isMember; + // + // Ext + // + + @Property("readonly, nonatomic") + private int creatorId; @Nullable @Property("readonly, nonatomic") private String theme; @@ -52,16 +66,16 @@ public class Group extends WrapperEntity implements KeyValueItem { @SuppressWarnings("NullableProblems") private List members; - public Group(@NotNull ApiGroup group) { - super(RECORD_ID, group); + public Group(@NotNull ApiGroup group, @Nullable ApiGroupFull ext) { + super(RECORD_ID, RECORD_EXT_ID, group, ext); } public Group(@NotNull byte[] data) throws IOException { - super(RECORD_ID, data); + super(RECORD_ID, RECORD_EXT_ID, data); } private Group() { - super(RECORD_ID); + super(RECORD_ID, RECORD_EXT_ID); } public Peer peer() { @@ -86,6 +100,18 @@ public Avatar getAvatar() { return avatar; } + public boolean isHidden() { + return isHidden; + } + + public int getMembersCount() { + return membersCount; + } + + public boolean isMember() { + return isMember; + } + @NotNull public List getMembers() { return members; @@ -105,230 +131,223 @@ public int getCreatorId() { return creatorId; } - public boolean isHidden() { - return isHidden; - } - public Group clearMembers() { + public Group editTitle(String title) { ApiGroup w = getWrapped(); ApiGroup res = new ApiGroup( w.getId(), w.getAccessHash(), - w.getTitle(), + title, w.getAvatar(), w.getMembersCount(), w.isMember(), w.isHidden(), w.getGroupType(), w.canSendMessage(), - w.getExt(), - - w.isAdmin(), - w.getCreatorUid(), - new ArrayList<>(), - w.getCreateDate(), - w.getTheme(), - w.getAbout()); + w.getExt()); res.setUnmappedObjects(w.getUnmappedObjects()); - return new Group(res); + return new Group(res, getWrappedExt()); } - public Group removeMember(int uid) { + public Group editAvatar(ApiAvatar avatar) { ApiGroup w = getWrapped(); - ArrayList nMembers = new ArrayList<>(); - for (ApiMember member : w.getMembers()) { - if (member.getUid() != uid) { - nMembers.add(member); - } - } - ApiGroup res = new ApiGroup( w.getId(), w.getAccessHash(), w.getTitle(), - w.getAvatar(), + avatar, w.getMembersCount(), w.isMember(), w.isHidden(), w.getGroupType(), w.canSendMessage(), - w.getExt(), - - w.isAdmin(), - w.getCreatorUid(), - nMembers, - w.getCreateDate(), - w.getTheme(), - w.getAbout()); + w.getExt()); res.setUnmappedObjects(w.getUnmappedObjects()); - return new Group(res); + return new Group(res, getWrappedExt()); } - public Group addMember(int uid, int inviterUid, long inviteDate) { - ApiGroup w = getWrapped(); - ArrayList nMembers = new ArrayList<>(); - for (ApiMember member : w.getMembers()) { - if (member.getUid() != uid) { - nMembers.add(member); - } - } - nMembers.add(new ApiMember(uid, inviterUid, inviteDate, null)); - ApiGroup res = new ApiGroup( - w.getId(), - w.getAccessHash(), - w.getTitle(), - w.getAvatar(), - w.getMembersCount(), - w.isMember(), - w.isHidden(), - w.getGroupType(), - w.canSendMessage(), - w.getExt(), - - w.isAdmin(), - w.getCreatorUid(), - nMembers, - w.getCreateDate(), - w.getTheme(), - w.getAbout()); - res.setUnmappedObjects(w.getUnmappedObjects()); - return new Group(res); + public Group editTheme(String theme) { +// ApiGroup w = getWrapped(); +// ApiGroup res = new ApiGroup( +// w.getId(), +// w.getAccessHash(), +// w.getTitle(), +// w.getAvatar(), +// w.getMembersCount(), +// w.isMember(), +// w.isHidden(), +// w.getGroupType(), +// w.canSendMessage(), +// w.getExt(), +// +// w.isAdmin(), +// w.getCreatorUid(), +// w.getMembers(), +// w.getCreateDate(), +// theme, +// w.getAbout()); +// res.setUnmappedObjects(w.getUnmappedObjects()); +// return new Group(res); + return this; } - public Group updateMembers(List nMembers) { - ApiGroup w = getWrapped(); - ApiGroup res = new ApiGroup( - w.getId(), - w.getAccessHash(), - w.getTitle(), - w.getAvatar(), - w.getMembersCount(), - w.isMember(), - w.isHidden(), - w.getGroupType(), - w.canSendMessage(), - w.getExt(), - - w.isAdmin(), - w.getCreatorUid(), - nMembers, - w.getCreateDate(), - w.getTheme(), - w.getAbout()); - res.setUnmappedObjects(w.getUnmappedObjects()); - return new Group(res); + public Group editAbout(String about) { +// ApiGroup w = getWrapped(); +// ApiGroup res = new ApiGroup( +// w.getId(), +// w.getAccessHash(), +// w.getTitle(), +// w.getAvatar(), +// w.getMembersCount(), +// w.isMember(), +// w.isHidden(), +// w.getGroupType(), +// w.canSendMessage(), +// w.getExt(), +// +// w.isAdmin(), +// w.getCreatorUid(), +// w.getMembers(), +// w.getCreateDate(), +// w.getTheme(), +// about); +// res.setUnmappedObjects(w.getUnmappedObjects()); +// return new Group(res); + return this; } - public Group editTitle(String title) { - ApiGroup w = getWrapped(); - ApiGroup res = new ApiGroup( - w.getId(), - w.getAccessHash(), - title, - w.getAvatar(), - w.getMembersCount(), - w.isMember(), - w.isHidden(), - w.getGroupType(), - w.canSendMessage(), - w.getExt(), - - w.isAdmin(), - w.getCreatorUid(), - w.getMembers(), - w.getCreateDate(), - w.getTheme(), - w.getAbout()); - res.setUnmappedObjects(w.getUnmappedObjects()); - return new Group(res); + + public Group clearMembers() { +// ApiGroup w = getWrapped(); +// ApiGroup res = new ApiGroup( +// w.getId(), +// w.getAccessHash(), +// w.getTitle(), +// w.getAvatar(), +// w.getMembersCount(), +// w.isMember(), +// w.isHidden(), +// w.getGroupType(), +// w.canSendMessage(), +// w.getExt(), +// +// w.isAdmin(), +// w.getCreatorUid(), +// new ArrayList<>(), +// w.getCreateDate(), +// w.getTheme(), +// w.getAbout()); +// res.setUnmappedObjects(w.getUnmappedObjects()); +// return new Group(res); + return this; } - public Group editTheme(String theme) { - ApiGroup w = getWrapped(); - ApiGroup res = new ApiGroup( - w.getId(), - w.getAccessHash(), - w.getTitle(), - w.getAvatar(), - w.getMembersCount(), - w.isMember(), - w.isHidden(), - w.getGroupType(), - w.canSendMessage(), - w.getExt(), - - w.isAdmin(), - w.getCreatorUid(), - w.getMembers(), - w.getCreateDate(), - theme, - w.getAbout()); - res.setUnmappedObjects(w.getUnmappedObjects()); - return new Group(res); + public Group removeMember(int uid) { +// ApiGroup w = getWrapped(); +// ArrayList nMembers = new ArrayList<>(); +// for (ApiMember member : w.getMembers()) { +// if (member.getUid() != uid) { +// nMembers.add(member); +// } +// } +// +// ApiGroup res = new ApiGroup( +// w.getId(), +// w.getAccessHash(), +// w.getTitle(), +// w.getAvatar(), +// w.getMembersCount(), +// w.isMember(), +// w.isHidden(), +// w.getGroupType(), +// w.canSendMessage(), +// w.getExt(), +// +// w.isAdmin(), +// w.getCreatorUid(), +// nMembers, +// w.getCreateDate(), +// w.getTheme(), +// w.getAbout()); +// res.setUnmappedObjects(w.getUnmappedObjects()); +// return new Group(res); + return this; } - public Group editAbout(String about) { - ApiGroup w = getWrapped(); - ApiGroup res = new ApiGroup( - w.getId(), - w.getAccessHash(), - w.getTitle(), - w.getAvatar(), - w.getMembersCount(), - w.isMember(), - w.isHidden(), - w.getGroupType(), - w.canSendMessage(), - w.getExt(), - - w.isAdmin(), - w.getCreatorUid(), - w.getMembers(), - w.getCreateDate(), - w.getTheme(), - about); - res.setUnmappedObjects(w.getUnmappedObjects()); - return new Group(res); + public Group addMember(int uid, int inviterUid, long inviteDate) { +// ApiGroup w = getWrapped(); +// ArrayList nMembers = new ArrayList<>(); +// for (ApiMember member : w.getMembers()) { +// if (member.getUid() != uid) { +// nMembers.add(member); +// } +// } +// nMembers.add(new ApiMember(uid, inviterUid, inviteDate, null)); +// ApiGroup res = new ApiGroup( +// w.getId(), +// w.getAccessHash(), +// w.getTitle(), +// w.getAvatar(), +// w.getMembersCount(), +// w.isMember(), +// w.isHidden(), +// w.getGroupType(), +// w.canSendMessage(), +// w.getExt(), +// +// w.isAdmin(), +// w.getCreatorUid(), +// nMembers, +// w.getCreateDate(), +// w.getTheme(), +// w.getAbout()); +// res.setUnmappedObjects(w.getUnmappedObjects()); +// return new Group(res); + return this; } - public Group editAvatar(ApiAvatar avatar) { - ApiGroup w = getWrapped(); - ApiGroup res = new ApiGroup( - w.getId(), - w.getAccessHash(), - w.getTitle(), - avatar, - w.getMembersCount(), - w.isMember(), - w.isHidden(), - w.getGroupType(), - w.canSendMessage(), - w.getExt(), - - w.isAdmin(), - w.getCreatorUid(), - w.getMembers(), - w.getCreateDate(), - w.getTheme(), - w.getAbout()); - res.setUnmappedObjects(w.getUnmappedObjects()); - return new Group(res); + public Group updateMembers(List nMembers) { +// ApiGroup w = getWrapped(); +// ApiGroup res = new ApiGroup( +// w.getId(), +// w.getAccessHash(), +// w.getTitle(), +// w.getAvatar(), +// w.getMembersCount(), +// w.isMember(), +// w.isHidden(), +// w.getGroupType(), +// w.canSendMessage(), +// w.getExt(), +// +// w.isAdmin(), +// w.getCreatorUid(), +// nMembers, +// w.getCreateDate(), +// w.getTheme(), +// w.getAbout()); +// res.setUnmappedObjects(w.getUnmappedObjects()); +// return new Group(res); + return this; } @Override - protected void applyWrapped(@NotNull ApiGroup wrapped) { + protected void applyWrapped(@NotNull ApiGroup wrapped, @Nullable ApiGroupFull ext) { this.groupId = wrapped.getId(); this.accessHash = wrapped.getAccessHash(); this.title = wrapped.getTitle(); this.avatar = wrapped.getAvatar() != null ? new Avatar(wrapped.getAvatar()) : null; - this.creatorId = wrapped.getCreatorUid(); - this.members = new ArrayList<>(); - for (ApiMember m : wrapped.getMembers()) { - this.members.add(new GroupMember(m.getUid(), m.getInviterUid(), m.getDate(), m.isAdmin() != null ? m.isAdmin() : m.getUid() == this.creatorId)); - } this.isHidden = wrapped.isHidden() != null ? wrapped.isHidden() : false; - this.about = wrapped.getAbout(); - this.theme = wrapped.getTheme(); + this.membersCount = wrapped.getMembersCount(); + this.isMember = wrapped.isMember(); + + // this.creatorId = wrapped.getCreatorUid(); + // this.members = new ArrayList<>(); + // for (ApiMember m : wrapped.getMembers()) { + //this.members.add(new GroupMember(m.getUid(), m.getInviterUid(), m.getDate(), m.isAdmin() != null ? m.isAdmin() : m.getUid() == this.creatorId)); + // } + // this.about = wrapped.getAbout(); + // this.theme = wrapped.getTheme(); } @Override @@ -351,6 +370,7 @@ public void serialize(BserWriter writer) throws IOException { super.serialize(writer); } + @Override public long getEngineId() { return groupId; @@ -362,4 +382,8 @@ protected ApiGroup createInstance() { return new ApiGroup(); } + @Override + protected ApiGroupFull createExtInstance() { + return new ApiGroupFull(); + } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/api/ApiSupportConfiguration.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/api/ApiSupportConfiguration.java index 38f9632862..53442bee8e 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/api/ApiSupportConfiguration.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/api/ApiSupportConfiguration.java @@ -15,6 +15,7 @@ public class ApiSupportConfiguration { opts.add(ApiUpdateOptimization.STRIP_ENTITIES); opts.add(ApiUpdateOptimization.STRIP_COUNTERS); opts.add(ApiUpdateOptimization.COMPACT_USERS); + opts.add(ApiUpdateOptimization.GROUPS_V2); OPTIMIZATIONS = Collections.unmodifiableList(opts); } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsModule.java index 80530edaa4..941cb5edc4 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsModule.java @@ -82,7 +82,7 @@ public class GroupsModule extends AbsModule { public GroupsModule(final ModuleContext context) { super(context); - collection = Storage.createKeyValue(STORAGE_GROUPS, GroupVM.CREATOR(context.getAuthModule().myUid()), Group.CREATOR); + collection = Storage.createKeyValue(STORAGE_GROUPS, GroupVM.CREATOR, Group.CREATOR); groups = collection.getEngine(); groupRouterInt = new GroupRouterInt(context); diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/router/GroupRouter.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/router/GroupRouter.java index 0c7cac97eb..e71fe138aa 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/router/GroupRouter.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/router/GroupRouter.java @@ -296,7 +296,7 @@ private Promise applyGroups(List groups) { .then(x -> { List res = new ArrayList<>(); for (Tuple2 u : x) { - res.add(new Group(u.getT1())); + res.add(new Group(u.getT1(), null)); } if (res.size() > 0) { groups().addOrUpdateItems(res); diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java index 0e6ae68e6e..25c041e695 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java @@ -13,11 +13,13 @@ import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; +import java.util.concurrent.CopyOnWriteArrayList; import im.actor.core.entity.Group; import im.actor.core.entity.GroupMember; import im.actor.core.viewmodel.generics.AvatarValueModel; import im.actor.core.viewmodel.generics.BooleanValueModel; +import im.actor.core.viewmodel.generics.IntValueModel; import im.actor.core.viewmodel.generics.StringValueModel; import im.actor.runtime.annotations.MainThread; import im.actor.runtime.mvvm.BaseValueModel; @@ -30,45 +32,41 @@ */ public class GroupVM extends BaseValueModel { - public static ValueModelCreator CREATOR(final int myUid) { - return new ValueModelCreator() { - @Override - public GroupVM create(Group baseValue) { - return new GroupVM(baseValue, myUid); - } - }; - } + public static ValueModelCreator CREATOR = GroupVM::new; @Property("nonatomic, readonly") private int groupId; + @NotNull @Property("nonatomic, readonly") - private int creatorId; + private StringValueModel name; @NotNull @Property("nonatomic, readonly") private AvatarValueModel avatar; @NotNull @Property("nonatomic, readonly") - private StringValueModel name; + private BooleanValueModel isMember; @NotNull @Property("nonatomic, readonly") - private BooleanValueModel isMember; + private IntValueModel membersCount; + @NotNull @Property("nonatomic, readonly") private ValueModel> members; + + @Property("nonatomic, readonly") + private int creatorId; @NotNull @Property("nonatomic, readonly") private ValueModel presence; - @Nullable + @NotNull @Property("nonatomic, readonly") private StringValueModel theme; - @Nullable + @NotNull @Property("nonatomic, readonly") private StringValueModel about; - private int myUid; - @NotNull - private ArrayList> listeners = new ArrayList>(); + private CopyOnWriteArrayList> listeners = new CopyOnWriteArrayList<>(); /** *

INTERNAL API

@@ -76,16 +74,17 @@ public GroupVM create(Group baseValue) { * * @param rawObj initial value of Group */ - public GroupVM(@NotNull Group rawObj, int myUid) { + public GroupVM(@NotNull Group rawObj) { super(rawObj); - this.myUid = myUid; this.groupId = rawObj.getGroupId(); this.creatorId = rawObj.getCreatorId(); this.name = new StringValueModel("group." + groupId + ".title", rawObj.getTitle()); this.avatar = new AvatarValueModel("group." + groupId + ".avatar", rawObj.getAvatar()); - this.isMember = new BooleanValueModel("group." + groupId + ".isMember", isHaveMember(myUid, rawObj.getMembers())); - this.members = new ValueModel>("group." + groupId + ".members", new HashSet(rawObj.getMembers())); - this.presence = new ValueModel("group." + groupId + ".presence", 0); + this.isMember = new BooleanValueModel("group." + groupId + ".isMember", rawObj.isMember()); + this.membersCount = new IntValueModel("group." + groupId + ".membersCount", rawObj.getMembersCount()); + + this.members = new ValueModel<>("group." + groupId + ".members", new HashSet<>()); + this.presence = new ValueModel<>("group." + groupId + ".presence", 0); this.theme = new StringValueModel("group." + groupId + ".theme", rawObj.getTheme()); this.about = new StringValueModel("group." + groupId + ".about", rawObj.getAbout()); } @@ -100,26 +99,6 @@ public int getId() { return groupId; } - /** - * Get Group creator user id - * - * @return creator user id - */ - @ObjectiveCName("getCreatorId") - public int getCreatorId() { - return creatorId; - } - - /** - * Get Group members count - * - * @return members count - */ - @ObjectiveCName("getMembersCount") - public int getMembersCount() { - return members.get().size(); - } - /** * Get Name Value Model * @@ -153,6 +132,29 @@ public BooleanValueModel isMember() { return isMember; } + /** + * Get Group members count + * + * @return members count + */ + @NotNull + @ObjectiveCName("getMembersCountModel") + public IntValueModel getMembersCount() { + return membersCount; + } + + + + /** + * Get Group creator user id + * + * @return creator user id + */ + @ObjectiveCName("getCreatorId") + public int getCreatorId() { + return creatorId; + } + /** * Get members Value Model * @@ -178,11 +180,13 @@ public ValueModel getPresence() { @Override protected void updateValues(@NotNull Group rawObj) { boolean isChanged = name.change(rawObj.getTitle()); + isChanged |= avatar.change(rawObj.getAvatar()); + isChanged |= membersCount.change(rawObj.getMembersCount()); + isChanged |= isMember.change(rawObj.isMember()); + isChanged |= theme.change(rawObj.getTheme()); isChanged |= about.change(rawObj.getAbout()); - isChanged |= avatar.change(rawObj.getAvatar()); - isChanged |= isMember.change(isHaveMember(myUid, rawObj.getMembers())); - isChanged |= members.change(new HashSet(rawObj.getMembers())); + isChanged |= members.change(new HashSet<>()); if (isChanged) { notifyChange(); @@ -194,7 +198,7 @@ protected void updateValues(@NotNull Group rawObj) { * * @return Value Model of String */ - @Nullable + @NotNull @ObjectiveCName("getAboutModel") public StringValueModel getAbout() { return about; @@ -205,7 +209,7 @@ public StringValueModel getAbout() { * * @return Value Model of String */ - @Nullable + @NotNull @ObjectiveCName("getThemeModel") public StringValueModel getTheme() { return theme; @@ -219,7 +223,7 @@ public StringValueModel getTheme() { @MainThread @ObjectiveCName("subscribeWithListener:") public void subscribe(@NotNull ModelChangedListener listener) { - im.actor.runtime.Runtime.checkMainThread(); + // im.actor.runtime.Runtime.checkMainThread(); if (listeners.contains(listener)) { return; } @@ -235,7 +239,7 @@ public void subscribe(@NotNull ModelChangedListener listener) { @MainThread @ObjectiveCName("subscribeWithListener:withNotify:") public void subscribe(@NotNull ModelChangedListener listener, boolean notify) { - im.actor.runtime.Runtime.checkMainThread(); + // im.actor.runtime.Runtime.checkMainThread(); if (listeners.contains(listener)) { return; } @@ -253,17 +257,14 @@ public void subscribe(@NotNull ModelChangedListener listener, boolean n @MainThread @ObjectiveCName("unsubscribeWithListener:") public void unsubscribe(@NotNull ModelChangedListener listener) { - im.actor.runtime.Runtime.checkMainThread(); + // im.actor.runtime.Runtime.checkMainThread(); listeners.remove(listener); } private void notifyChange() { - im.actor.runtime.Runtime.postToMainThread(new Runnable() { - @Override - public void run() { - for (ModelChangedListener l : listeners.toArray(new ModelChangedListener[listeners.size()])) { - l.onChanged(GroupVM.this); - } + im.actor.runtime.Runtime.postToMainThread(() -> { + for (ModelChangedListener l : listeners) { + l.onChanged(GroupVM.this); } }); } diff --git a/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/mvvm/ValueModel.java b/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/mvvm/ValueModel.java index 729c0e64a9..d18980bb1c 100644 --- a/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/mvvm/ValueModel.java +++ b/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/mvvm/ValueModel.java @@ -3,6 +3,8 @@ import com.google.j2objc.annotations.ObjectiveCName; import com.google.j2objc.annotations.Property; +import org.jetbrains.annotations.Nullable; + public class ValueModel extends Value { @Property("nonatomic, readonly") @@ -26,7 +28,7 @@ public T get() { * @return is value changed */ @ObjectiveCName("changeWithValue:") - public boolean change(T value) { + public boolean change(@Nullable T value) { if (this.value != null && value != null && value.equals(this.value)) { return false; } @@ -40,7 +42,7 @@ public boolean change(T value) { } @ObjectiveCName("changeNoNotificationWithValue:") - public boolean changeNoNotification(T value) { + public boolean changeNoNotification(@Nullable T value) { if (this.value != null && value != null && value.equals(this.value)) { return false; } @@ -52,7 +54,7 @@ public boolean changeNoNotification(T value) { } @ObjectiveCName("changeInUIThreadWithValue:") - protected boolean changeInUIThread(T value) { + protected boolean changeInUIThread(@Nullable T value) { if (this.value != null && value != null && value.equals(this.value)) { return false; } From 12b7e8791c0e1c9a1a8465d5649d7a0d90e50696 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Wed, 13 Jul 2016 02:02:29 +0300 Subject: [PATCH 002/414] wip(core): Migration to Group V2 * Processing of new updates for Group entity * Loading FullGroup --- .../models/im/actor/api/scheme.mps | 2 +- .../im/actor/sdk/controllers/ActorBinder.java | 39 ++- .../controllers/group/GroupInfoFragment.java | 14 +- .../main/java/im/actor/core/entity/Group.java | 180 +++++++++++-- .../java/im/actor/core/entity/GroupType.java | 5 + .../main/java/im/actor/core/entity/User.java | 1 - .../java/im/actor/core/modules/Modules.java | 2 + .../core/modules/groups/GroupsModule.java | 34 ++- .../modules/groups/router/GroupRouter.java | 251 ++++++++++++------ .../modules/groups/router/GroupRouterInt.java | 5 + .../router/entity/RouterLoadFullGroup.java | 14 + .../messaging/actions/SenderActor.java | 24 +- .../avatar/GroupAvatarChangeActor.java | 7 +- .../core/modules/users/router/UserRouter.java | 6 +- .../java/im/actor/core/viewmodel/GroupVM.java | 15 +- 15 files changed, 428 insertions(+), 171 deletions(-) create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/GroupType.java create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/router/entity/RouterLoadFullGroup.java diff --git a/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps b/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps index cec1dde0e6..a3b984aa9c 100644 --- a/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps +++ b/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps @@ -6969,7 +6969,7 @@ - + diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/ActorBinder.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/ActorBinder.java index 138a04f470..6ab9a568ea 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/ActorBinder.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/ActorBinder.java @@ -167,7 +167,6 @@ public void onChanged(UserPresence val, Value valueModel) { public Binding bind(final OnChangedListener callback, ValueModel up) { return bind(up, new ValueChangedListener() { - @Override public void onChanged(UserPresence val, Value valueModel) { callback.onChanged(val.getState().equals(UserPresence.State.ONLINE)); @@ -176,29 +175,25 @@ public void onChanged(UserPresence val, Value valueModel) { } public void bind(final TextView textView, final View titleContainer, final GroupVM value) { - bind(value.getPresence(), value.getMembers(), value.isMember(), new ValueTripleChangedListener, Boolean>() { - @Override - public void onChanged(Integer online, Value onlineModel, - HashSet members, Value> membersModel, Boolean isMember, Value isMemberModel) { - if (isMember) { - titleContainer.setVisibility(View.VISIBLE); - if (online <= 0) { - SpannableStringBuilder builder = new SpannableStringBuilder( - messenger().getFormatter().formatGroupMembers(members.size())); - builder.setSpan(new ForegroundColorSpan(0xB7ffffff), 0, builder.length(), - Spanned.SPAN_INCLUSIVE_EXCLUSIVE); - textView.setText(builder); - } else { - SpannableStringBuilder builder = new SpannableStringBuilder( - messenger().getFormatter().formatGroupMembers(members.size()) + ", "); - builder.setSpan(new ForegroundColorSpan(0xB7ffffff), 0, builder.length(), - Spanned.SPAN_INCLUSIVE_EXCLUSIVE); - builder.append(messenger().getFormatter().formatGroupOnline(online)); - textView.setText(builder); - } + bind(value.getPresence(), value.getMembersCount(), value.isMember(), (online, onlineModel, membersCount, membersModel, isMember, isMemberModel) -> { + if (isMember) { + titleContainer.setVisibility(View.VISIBLE); + if (online <= 0) { + SpannableStringBuilder builder = new SpannableStringBuilder( + messenger().getFormatter().formatGroupMembers(membersCount)); + builder.setSpan(new ForegroundColorSpan(0xB7ffffff), 0, builder.length(), + Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + textView.setText(builder); } else { - titleContainer.setVisibility(View.GONE); + SpannableStringBuilder builder = new SpannableStringBuilder( + messenger().getFormatter().formatGroupMembers(membersCount) + ", "); + builder.setSpan(new ForegroundColorSpan(0xB7ffffff), 0, builder.length(), + Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + builder.append(messenger().getFormatter().formatGroupOnline(online)); + textView.setText(builder); } + } else { + titleContainer.setVisibility(View.GONE); } }); } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java index bdfa0fe42b..91337b195f 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java @@ -136,13 +136,13 @@ public void onClick(View view) { createdBy.setText(R.string.group_created_by_you); isAdmin = true; } else { - UserVM admin = users().get(groupInfo.getCreatorId()); - bind(admin.getName(), new ValueChangedListener() { - @Override - public void onChanged(String val, Value Value) { - createdBy.setText(getString(R.string.group_created_by).replace("{0}", val)); - } - }); +// UserVM admin = users().get(groupInfo.getCreatorId()); +// bind(admin.getName(), new ValueChangedListener() { +// @Override +// public void onChanged(String val, Value Value) { +// createdBy.setText(getString(R.string.group_created_by).replace("{0}", val)); +// } +// }); } //Description diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Group.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Group.java index 007120f56e..6227f359d9 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Group.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Group.java @@ -14,8 +14,10 @@ import java.util.List; import im.actor.core.api.ApiAvatar; +import im.actor.core.api.ApiFullUser; import im.actor.core.api.ApiGroup; import im.actor.core.api.ApiGroupFull; +import im.actor.core.api.ApiMapValue; import im.actor.core.api.ApiMember; import im.actor.runtime.bser.BserCreator; import im.actor.runtime.bser.BserValues; @@ -49,6 +51,12 @@ public class Group extends WrapperExtEntity implements K private int membersCount; @Property("readonly, nonatomic") private boolean isMember; + @Property("readonly, nonatomic") + private boolean canWrite; + @NotNull + @Property("readonly, nonatomic") + private GroupType groupType; + // // Ext // @@ -66,6 +74,13 @@ public class Group extends WrapperExtEntity implements K @SuppressWarnings("NullableProblems") private List members; + @Property("readonly, nonatomic") + private boolean haveExtension; + + // + // Constructors + // + public Group(@NotNull ApiGroup group, @Nullable ApiGroupFull ext) { super(RECORD_ID, RECORD_EXT_ID, group, ext); } @@ -78,9 +93,9 @@ private Group() { super(RECORD_ID, RECORD_EXT_ID); } - public Peer peer() { - return new Peer(PeerType.GROUP, groupId); - } + // + // Getters + // public int getGroupId() { return groupId; @@ -112,6 +127,19 @@ public boolean isMember() { return isMember; } + public boolean isCanWrite() { + return canWrite; + } + + @NotNull + public GroupType getGroupType() { + return groupType; + } + + // + // Ext + // + @NotNull public List getMembers() { return members; @@ -131,6 +159,17 @@ public int getCreatorId() { return creatorId; } + public boolean isHaveExtension() { + return haveExtension; + } + + public Group updateExt(@Nullable ApiGroupFull ext) { + return new Group(getWrapped(), ext); + } + + // + // Editing Main + // public Group editTitle(String title) { ApiGroup w = getWrapped(); @@ -166,6 +205,78 @@ public Group editAvatar(ApiAvatar avatar) { return new Group(res, getWrappedExt()); } + public Group editIsMember(boolean isMember) { + ApiGroup w = getWrapped(); + ApiGroup res = new ApiGroup( + w.getId(), + w.getAccessHash(), + w.getTitle(), + w.getAvatar(), + w.getMembersCount(), + isMember, + w.isHidden(), + w.getGroupType(), + w.canSendMessage(), + w.getExt()); + res.setUnmappedObjects(w.getUnmappedObjects()); + return new Group(res, getWrappedExt()); + } + + public Group editExt(ApiMapValue ext) { + ApiGroup w = getWrapped(); + ApiGroup res = new ApiGroup( + w.getId(), + w.getAccessHash(), + w.getTitle(), + w.getAvatar(), + w.getMembersCount(), + w.isMember(), + w.isHidden(), + w.getGroupType(), + w.canSendMessage(), + ext); + res.setUnmappedObjects(w.getUnmappedObjects()); + return new Group(res, getWrappedExt()); + } + + public Group editCanWrite(boolean canWrite) { + ApiGroup w = getWrapped(); + ApiGroup res = new ApiGroup( + w.getId(), + w.getAccessHash(), + w.getTitle(), + w.getAvatar(), + w.getMembersCount(), + w.isMember(), + w.isHidden(), + w.getGroupType(), + canWrite, + w.getExt()); + res.setUnmappedObjects(w.getUnmappedObjects()); + return new Group(res, getWrappedExt()); + } + + public Group editMembersCount(int membersCount) { + ApiGroup w = getWrapped(); + ApiGroup res = new ApiGroup( + w.getId(), + w.getAccessHash(), + w.getTitle(), + w.getAvatar(), + membersCount, + w.isMember(), + w.isHidden(), + w.getGroupType(), + w.canSendMessage(), + w.getExt()); + res.setUnmappedObjects(w.getUnmappedObjects()); + return new Group(res, getWrappedExt()); + } + + // + // Editing Ext + // + public Group editTheme(String theme) { // ApiGroup w = getWrapped(); // ApiGroup res = new ApiGroup( @@ -333,13 +444,49 @@ public Group updateMembers(List nMembers) { @Override protected void applyWrapped(@NotNull ApiGroup wrapped, @Nullable ApiGroupFull ext) { + this.groupId = wrapped.getId(); this.accessHash = wrapped.getAccessHash(); this.title = wrapped.getTitle(); this.avatar = wrapped.getAvatar() != null ? new Avatar(wrapped.getAvatar()) : null; this.isHidden = wrapped.isHidden() != null ? wrapped.isHidden() : false; this.membersCount = wrapped.getMembersCount(); - this.isMember = wrapped.isMember(); + this.isMember = wrapped.isMember() != null ? wrapped.isMember() : true; + + if (wrapped.getGroupType() == null) { + this.groupType = GroupType.GROUP; + } else { + switch (wrapped.getGroupType()) { + case CHANNEL: + this.groupType = GroupType.CHANNEL; + break; + case GROUP: + this.groupType = GroupType.GROUP; + break; + default: + case UNSUPPORTED_VALUE: + this.groupType = GroupType.OTHER; + break; + } + } + + if (wrapped.canSendMessage() != null) { + this.canWrite = wrapped.canSendMessage(); + } else { + // True is default for groups and false otherwise + this.canWrite = this.groupType == GroupType.GROUP; + } + + + // + // Ext + // + + if (ext != null) { + haveExtension = true; + } else { + haveExtension = false; + } // this.creatorId = wrapped.getCreatorUid(); // this.members = new ArrayList<>(); @@ -350,32 +497,15 @@ protected void applyWrapped(@NotNull ApiGroup wrapped, @Nullable ApiGroupFull ex // this.theme = wrapped.getTheme(); } - @Override - public void parse(BserValues values) throws IOException { - // Is Wrapper Layout - if (values.getBool(9, false)) { - // Parse wrapper layout - super.parse(values); - } else { - // Convert old layout - throw new IOException("Unsupported obsolete format"); - } - } - - @Override - public void serialize(BserWriter writer) throws IOException { - // Mark as wrapper layout - writer.writeBool(9, true); - // Serialize wrapper layout - super.serialize(writer); - } - - @Override public long getEngineId() { return groupId; } + public Peer peer() { + return new Peer(PeerType.GROUP, groupId); + } + @Override @NotNull protected ApiGroup createInstance() { diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/GroupType.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/GroupType.java new file mode 100644 index 0000000000..9ab166cbf7 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/GroupType.java @@ -0,0 +1,5 @@ +package im.actor.core.entity; + +public enum GroupType { + GROUP, CHANNEL, OTHER +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/User.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/User.java index 5623dca401..2e2462fab4 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/User.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/User.java @@ -81,7 +81,6 @@ public class User extends WrapperExtEntity implements KeyV private List commands; - @NotNull @Property("readonly, nonatomic") private boolean haveExtension; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/Modules.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/Modules.java index fd17ce01d0..618cefdb2f 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/Modules.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/Modules.java @@ -176,6 +176,8 @@ public void onLoggedIn(boolean first) { timing = new Timing("ACCOUNT_RUN"); timing.section("Users"); users.run(); + timing.section("Groups"); + groups.run(); timing.section("Settings"); settings.run(); timing.section("DeviceInfo"); diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsModule.java index 941cb5edc4..6221c5dd8d 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsModule.java @@ -51,7 +51,12 @@ import im.actor.core.api.updates.UpdateGroupUserLeaveObsolete; import im.actor.core.api.updates.UpdateMessage; import im.actor.core.entity.Group; +import im.actor.core.entity.Peer; +import im.actor.core.entity.PeerType; import im.actor.core.entity.User; +import im.actor.core.events.PeerChatOpened; +import im.actor.core.events.PeerInfoOpened; +import im.actor.core.events.UserVisible; import im.actor.core.modules.AbsModule; import im.actor.core.modules.ModuleContext; import im.actor.core.modules.api.ApiSupportConfiguration; @@ -63,6 +68,8 @@ import im.actor.runtime.Storage; import im.actor.runtime.actors.ActorRef; import im.actor.runtime.actors.messages.Void; +import im.actor.runtime.eventbus.BusSubscriber; +import im.actor.runtime.eventbus.Event; import im.actor.runtime.function.Function; import im.actor.runtime.mvvm.MVVMCollection; import im.actor.runtime.promise.Promise; @@ -71,7 +78,7 @@ import static im.actor.runtime.actors.ActorSystem.system; -public class GroupsModule extends AbsModule { +public class GroupsModule extends AbsModule implements BusSubscriber { private final KeyValueEngine groups; private final MVVMCollection collection; @@ -91,6 +98,10 @@ public GroupsModule(final ModuleContext context) { avatarChangeActor = system().actorOf("actor/avatar/group", () -> new GroupAvatarChangeActor(context)); } + public void run() { + context().getEvents().subscribe(this, PeerChatOpened.EVENT); + context().getEvents().subscribe(this, PeerInfoOpened.EVENT); + } // // Storage @@ -232,11 +243,7 @@ public Promise editTitle(final int gid, final String name) { .flatMap(responseSeqDate -> updates().applyUpdate( responseSeqDate.getSeq(), responseSeqDate.getState(), - new UpdateGroupTitleChangedObsolete( - gid, rid, - myUid(), name, - responseSeqDate.getDate())) - ); + new UpdateGroupTitleChanged(gid, name))); } public Promise editTheme(final int gid, final String theme) { @@ -355,4 +362,19 @@ public Promise revokeIntegrationToken(final int gid) { public void resetModule() { groups.clear(); } + + @Override + public void onBusEvent(Event event) { + if (event instanceof PeerChatOpened) { + Peer peer = ((PeerChatOpened) event).getPeer(); + if (peer.getPeerType() == PeerType.GROUP) { + getRouter().onFullGroupNeeded(peer.getPeerId()); + } + } else if (event instanceof PeerInfoOpened) { + Peer peer = ((PeerInfoOpened) event).getPeer(); + if (peer.getPeerType() == PeerType.GROUP) { + getRouter().onFullGroupNeeded(peer.getPeerId()); + } + } + } } \ No newline at end of file diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/router/GroupRouter.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/router/GroupRouter.java index e71fe138aa..1fd3bb07d3 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/router/GroupRouter.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/router/GroupRouter.java @@ -3,39 +3,36 @@ import org.jetbrains.annotations.Nullable; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import im.actor.core.api.ApiAvatar; import im.actor.core.api.ApiGroup; import im.actor.core.api.ApiGroupOutPeer; +import im.actor.core.api.ApiMapValue; import im.actor.core.api.ApiMember; -import im.actor.core.api.ApiUser; -import im.actor.core.api.ApiUserOutPeer; -import im.actor.core.api.updates.UpdateGroupAboutChanged; +import im.actor.core.api.rpc.RequestLoadFullGroups; import im.actor.core.api.updates.UpdateGroupAboutChangedObsolete; import im.actor.core.api.updates.UpdateGroupAvatarChanged; -import im.actor.core.api.updates.UpdateGroupAvatarChangedObsolete; -import im.actor.core.api.updates.UpdateGroupInvite; +import im.actor.core.api.updates.UpdateGroupCanSendMessagesChanged; +import im.actor.core.api.updates.UpdateGroupExtChanged; import im.actor.core.api.updates.UpdateGroupInviteObsolete; +import im.actor.core.api.updates.UpdateGroupMemberAdminChanged; +import im.actor.core.api.updates.UpdateGroupMemberChanged; +import im.actor.core.api.updates.UpdateGroupMemberDiff; +import im.actor.core.api.updates.UpdateGroupMembersBecameAsync; +import im.actor.core.api.updates.UpdateGroupMembersCountChanged; import im.actor.core.api.updates.UpdateGroupMembersUpdate; import im.actor.core.api.updates.UpdateGroupMembersUpdateObsolete; import im.actor.core.api.updates.UpdateGroupTitleChanged; -import im.actor.core.api.updates.UpdateGroupTitleChangedObsolete; -import im.actor.core.api.updates.UpdateGroupTopicChanged; import im.actor.core.api.updates.UpdateGroupTopicChangedObsolete; -import im.actor.core.api.updates.UpdateGroupUserInvited; import im.actor.core.api.updates.UpdateGroupUserInvitedObsolete; -import im.actor.core.api.updates.UpdateGroupUserKick; import im.actor.core.api.updates.UpdateGroupUserKickObsolete; -import im.actor.core.api.updates.UpdateGroupUserLeave; import im.actor.core.api.updates.UpdateGroupUserLeaveObsolete; import im.actor.core.entity.Group; import im.actor.core.entity.Message; import im.actor.core.entity.MessageState; -import im.actor.core.entity.User; -import im.actor.core.entity.content.ServiceGroupAvatarChanged; import im.actor.core.entity.content.ServiceGroupCreated; -import im.actor.core.entity.content.ServiceGroupTitleChanged; import im.actor.core.entity.content.ServiceGroupUserInvited; import im.actor.core.entity.content.ServiceGroupUserKicked; import im.actor.core.entity.content.ServiceGroupUserLeave; @@ -44,6 +41,7 @@ import im.actor.core.modules.groups.router.entity.RouterApplyGroups; import im.actor.core.modules.groups.router.entity.RouterFetchMissingGroups; import im.actor.core.modules.groups.router.entity.RouterGroupUpdate; +import im.actor.core.modules.groups.router.entity.RouterLoadFullGroup; import im.actor.core.modules.messaging.router.RouterInt; import im.actor.core.network.parser.Update; import im.actor.runtime.actors.messages.Void; @@ -58,15 +56,69 @@ public class GroupRouter extends ModuleActor { // j2objc workaround private static final Void DUMB = null; + private final HashSet requestedFullGroups = new HashSet<>(); private boolean isFreezed = false; public GroupRouter(ModuleContext context) { super(context); } + // + // Updates Main + // + + @Verified + public Promise onAvatarChanged(int groupId, @Nullable ApiAvatar avatar) { + return forGroup(groupId, group -> { + Group upd = group.editAvatar(avatar); + groups().addOrUpdateItem(upd); + return onGroupDescChanged(upd); + }); + } + + @Verified + public Promise onTitleChanged(int groupId, String title) { + return forGroup(groupId, group -> { + Group upd = group.editTitle(title); + groups().addOrUpdateItem(upd); + return onGroupDescChanged(upd); + }); + } + + @Verified + public Promise onCanWriteMessagesChanged(int groupId, boolean canWrite) { + return forGroup(groupId, group -> { + Group upd = group.editCanWrite(canWrite); + groups().addOrUpdateItem(upd); + return Promise.success(null); + }); + } + + @Verified + public Promise onIsMemberChanged(int groupId, boolean isMember) { + return forGroup(groupId, group -> { + Group upd = group.editIsMember(isMember); + groups().addOrUpdateItem(upd); + return Promise.success(null); + }); + } + + @Verified + public Promise onExtChanged(int groupId, ApiMapValue ext) { + return forGroup(groupId, group -> { + Group upd = group.editExt(ext); + groups().addOrUpdateItem(upd); + return Promise.success(null); + }); + } // - // Updates + // Members Updates + // + + + // + // Updates Ext // @Verified @@ -163,31 +215,6 @@ public Promise onUserAdded(int groupId, long rid, int uid, int adder, long }); } - @Verified - public Promise onTitleChanged(int groupId, long rid, int uid, String title, long date, - boolean isSilent) { - return forGroup(groupId, group -> { - - // Change group title - Group upd = group.editTitle(title); - - // Update group - groups().addOrUpdateItem(upd); - - // Notify about group change - Promise src = onGroupDescChanged(upd); - - // Create message if needed - if (!isSilent) { - Message message = new Message(rid, date, date, uid, - uid == myUid() ? MessageState.SENT : MessageState.UNKNOWN, - ServiceGroupTitleChanged.create(title)); - src = src.chain(v -> getRouter().onNewMessage(group.peer(), message)); - } - return Promise.success(null); - }); - } - @Verified public Promise onTopicChanged(int groupId, String topic) { return forGroup(groupId, group -> { @@ -213,32 +240,6 @@ public Promise onAboutChanged(int groupId, String about) { }); } - @Verified - public Promise onAvatarChanged(int groupId, long rid, int uid, @Nullable ApiAvatar avatar, long date, - boolean isSilent) { - - return forGroup(groupId, group -> { - - // Change group avatar - Group upd = group.editAvatar(avatar); - - // Update group - groups().addOrUpdateItem(upd); - - // Notify about group change - Promise src = onGroupDescChanged(upd); - - // Create message if needed - if (!isSilent) { - Message message = new Message(rid, date, date, uid, - uid == myUid() ? MessageState.SENT : MessageState.UNKNOWN, - ServiceGroupAvatarChanged.create(avatar)); - src.chain(v -> getRouter().onNewMessage(group.peer(), message)); - } - return src; - }); - } - @Verified public Promise onMembersUpdated(int groupId, List members) { @@ -251,7 +252,7 @@ public Promise onMembersUpdated(int groupId, List members) { } private Promise forGroup(int groupId, Function> func) { - isFreezed = true; + freeze(); return groups().getValueAsync(groupId) .fallback(e -> null) .flatMap(g -> { @@ -260,9 +261,8 @@ private Promise forGroup(int groupId, Function> func) } return Promise.success(null); }) - .then(v -> { - isFreezed = false; - unstashAll(); + .after((v, e) -> { + unfreeze(); }); } @@ -273,21 +273,20 @@ private Promise forGroup(int groupId, Function> func) @Verified private Promise> fetchMissingGroups(List groups) { - isFreezed = true; + freeze(); return PromisesArray.of(groups) .map((Function>) u -> groups().containsAsync(u.getGroupId()) .map(v -> v ? null : u)) .filterNull() .zip() .after((r, e) -> { - isFreezed = false; - unstashAll(); + unfreeze(); }); } @Verified private Promise applyGroups(List groups) { - isFreezed = true; + freeze(); return PromisesArray.of(groups) .map((Function>>) u -> groups().containsAsync(u.getId()) .map(v -> new Tuple2<>(u, v))) @@ -303,10 +302,29 @@ private Promise applyGroups(List groups) { } }) .map(x -> (Void) null) - .after((r, e) -> { - isFreezed = false; - unstashAll(); - }); + .after((r, e) -> unfreeze()); + } + + private void onRequestLoadFullGroup(int gid) { + if (requestedFullGroups.contains(gid)) { + return; + } + requestedFullGroups.add(gid); + + freeze(); + groups().getValueAsync(gid) + .flatMap(group -> { + if (!group.isHaveExtension()) { + ArrayList groups = new ArrayList<>(); + groups.add(new ApiGroupOutPeer(gid, group.getAccessHash())); + return api(new RequestLoadFullGroups(groups)) + .map(r -> group.updateExt(r.getGroups().get(0))); + } else { + return Promise.failure(new RuntimeException("Already loaded")); + } + }) + .then(r -> groups().addOrUpdateItem(r)) + .after((r, e) -> unfreeze()); } @@ -323,28 +341,71 @@ private RouterInt getRouter() { return context().getMessagesModule().getRouter(); } + private void freeze() { + isFreezed = true; + } + + private void unfreeze() { + isFreezed = false; + unstashAll(); + } // // Messages // private Promise onUpdate(Update update) { - if (update instanceof UpdateGroupTitleChangedObsolete) { - UpdateGroupTitleChangedObsolete titleChanged = (UpdateGroupTitleChangedObsolete) update; - return onTitleChanged(titleChanged.getGroupId(), titleChanged.getRid(), - titleChanged.getUid(), titleChanged.getTitle(), titleChanged.getDate(), - false); - } else if (update instanceof UpdateGroupTopicChangedObsolete) { + + // + // Main + // + if (update instanceof UpdateGroupTitleChanged) { + UpdateGroupTitleChanged titleChanged = (UpdateGroupTitleChanged) update; + return onTitleChanged(titleChanged.getGroupId(), titleChanged.getTitle()); + } else if (update instanceof UpdateGroupAvatarChanged) { + UpdateGroupAvatarChanged avatarChanged = (UpdateGroupAvatarChanged) update; + return onAvatarChanged(avatarChanged.getGroupId(), avatarChanged.getAvatar()); + } else if (update instanceof UpdateGroupCanSendMessagesChanged) { + UpdateGroupCanSendMessagesChanged messagesChanged = (UpdateGroupCanSendMessagesChanged) update; + return onCanWriteMessagesChanged(messagesChanged.getGroupId(), messagesChanged.canSendMessages()); + } else if (update instanceof UpdateGroupMemberChanged) { + UpdateGroupMemberChanged memberChanged = (UpdateGroupMemberChanged) update; + return onIsMemberChanged(memberChanged.getGroupId(), memberChanged.isMember()); + } else if (update instanceof UpdateGroupExtChanged) { + UpdateGroupExtChanged extChanged = (UpdateGroupExtChanged) update; + return onExtChanged(extChanged.getGroupId(), extChanged.getExt()); + } + + // + // Members + // + else if (update instanceof UpdateGroupMembersUpdate) { + UpdateGroupMembersUpdate membersUpdate = (UpdateGroupMembersUpdate) update; + + } else if (update instanceof UpdateGroupMemberAdminChanged) { + UpdateGroupMemberAdminChanged adminChanged = (UpdateGroupMemberAdminChanged) update; + + } else if (update instanceof UpdateGroupMemberDiff) { + UpdateGroupMemberDiff memberDiff = (UpdateGroupMemberDiff) update; + + } else if (update instanceof UpdateGroupMembersBecameAsync) { + UpdateGroupMembersBecameAsync becameAsync = (UpdateGroupMembersBecameAsync) update; + + } else if (update instanceof UpdateGroupMembersCountChanged) { + UpdateGroupMembersCountChanged membersCountChanged = (UpdateGroupMembersCountChanged) update; + + } + + // + // Ext + // + + else if (update instanceof UpdateGroupTopicChangedObsolete) { UpdateGroupTopicChangedObsolete topicChanged = (UpdateGroupTopicChangedObsolete) update; return onTopicChanged(topicChanged.getGroupId(), topicChanged.getTopic()); } else if (update instanceof UpdateGroupAboutChangedObsolete) { UpdateGroupAboutChangedObsolete aboutChanged = (UpdateGroupAboutChangedObsolete) update; return onAboutChanged(aboutChanged.getGroupId(), aboutChanged.getAbout()); - } else if (update instanceof UpdateGroupAvatarChangedObsolete) { - UpdateGroupAvatarChangedObsolete avatarChanged = (UpdateGroupAvatarChangedObsolete) update; - return onAvatarChanged(avatarChanged.getGroupId(), avatarChanged.getRid(), - avatarChanged.getUid(), avatarChanged.getAvatar(), - avatarChanged.getDate(), false); } else if (update instanceof UpdateGroupInviteObsolete) { UpdateGroupInviteObsolete groupInvite = (UpdateGroupInviteObsolete) update; return onGroupInvite(groupInvite.getGroupId(), @@ -395,4 +456,18 @@ public Promise onAsk(Object message) throws Exception { return super.onAsk(message); } } + + @Override + public void onReceive(Object message) { + + if (message instanceof RouterLoadFullGroup) { + if (isFreezed) { + stash(); + return; + } + onRequestLoadFullGroup(((RouterLoadFullGroup) message).getGid()); + } else { + super.onReceive(message); + } + } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/router/GroupRouterInt.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/router/GroupRouterInt.java index db24b390f4..a6f07e8a3e 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/router/GroupRouterInt.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/router/GroupRouterInt.java @@ -8,6 +8,7 @@ import im.actor.core.modules.groups.router.entity.RouterApplyGroups; import im.actor.core.modules.groups.router.entity.RouterFetchMissingGroups; import im.actor.core.modules.groups.router.entity.RouterGroupUpdate; +import im.actor.core.modules.groups.router.entity.RouterLoadFullGroup; import im.actor.core.network.parser.Update; import im.actor.runtime.actors.ActorInterface; import im.actor.runtime.actors.messages.Void; @@ -32,4 +33,8 @@ public Promise> fetchPendingGroups(List p public Promise onUpdate(Update update) { return ask(new RouterGroupUpdate(update)); } + + public void onFullGroupNeeded(int gid) { + send(new RouterLoadFullGroup(gid)); + } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/router/entity/RouterLoadFullGroup.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/router/entity/RouterLoadFullGroup.java new file mode 100644 index 0000000000..a0240e431a --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/router/entity/RouterLoadFullGroup.java @@ -0,0 +1,14 @@ +package im.actor.core.modules.groups.router.entity; + +public class RouterLoadFullGroup { + + private int gid; + + public RouterLoadFullGroup(int gid) { + this.gid = gid; + } + + public int getGid() { + return gid; + } +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java index 098ef9dbf9..8deddc101d 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java @@ -150,17 +150,19 @@ public void doSendText(@NotNull Peer peer, @NotNull String text, if (peer.getPeerType() == PeerType.GROUP) { Group group = getGroup(peer.getPeerId()); String lowText = text.toLowerCase(); - for (GroupMember member : group.getMembers()) { - User user = getUser(member.getUid()); - if (user.getNick() != null) { - String nick = "@" + user.getNick().toLowerCase(); - // TODO: Better filtering - if (lowText.contains(nick + ":") - || lowText.contains(nick + " ") - || lowText.contains(" " + nick) - || lowText.endsWith(nick) - || lowText.equals(nick)) { - mentions.add(user.getUid()); + if (group.getMembers() != null) { + for (GroupMember member : group.getMembers()) { + User user = getUser(member.getUid()); + if (user.getNick() != null) { + String nick = "@" + user.getNick().toLowerCase(); + // TODO: Better filtering + if (lowText.contains(nick + ":") + || lowText.contains(nick + " ") + || lowText.contains(" " + nick) + || lowText.endsWith(nick) + || lowText.equals(nick)) { + mentions.add(user.getUid()); + } } } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/profile/avatar/GroupAvatarChangeActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/profile/avatar/GroupAvatarChangeActor.java index c4243f9a2f..df6fbbc424 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/profile/avatar/GroupAvatarChangeActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/profile/avatar/GroupAvatarChangeActor.java @@ -72,11 +72,8 @@ public void uploadCompleted(final long rid, FileReference fileReference) { updates().applyUpdate( responseEditGroupAvatar.getSeq(), responseEditGroupAvatar.getState(), - new UpdateGroupAvatarChangedObsolete( - gid, rid, myUid(), - responseEditGroupAvatar.getAvatar(), - responseEditGroupAvatar.getDate()) - )) + new UpdateGroupAvatarChanged( + gid, responseEditGroupAvatar.getAvatar()))) .then(v -> avatarChanged(gid, rid)) .failure(e -> { if (!tasksMap.containsKey(rid)) { diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/users/router/UserRouter.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/users/router/UserRouter.java index 6ac30a9e19..a3ded2dac2 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/users/router/UserRouter.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/users/router/UserRouter.java @@ -386,8 +386,7 @@ private void onLoadFullUser(int uid) { private Promise> fetchMissingUsers(List users) { freeze(); return PromisesArray.of(users) - .map((Function>) u -> users().containsAsync(u.getUid()) - .map(v -> v ? null : u)) + .map(u -> users().containsAsync(u.getUid()).map(v -> v ? null : u)) .filterNull() .zip() .after((r, e) -> unfreeze()); @@ -398,8 +397,7 @@ private Promise> fetchMissingUsers(List use private Promise applyUsers(List users) { freeze(); return PromisesArray.of(users) - .map((Function>>) u -> users().containsAsync(u.getId()) - .map(v -> new Tuple2<>(u, v))) + .map(u -> users().containsAsync(u.getId()).map(v -> new Tuple2<>(u, v))) .filter(t -> !t.getT2()) .zip() .then(x -> { diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java index 25c041e695..758101928a 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java @@ -48,6 +48,9 @@ public class GroupVM extends BaseValueModel { @NotNull @Property("nonatomic, readonly") private IntValueModel membersCount; + @NotNull + @Property("nonatomic, readonly") + private BooleanValueModel isCanWriteMessage; @NotNull @Property("nonatomic, readonly") @@ -82,6 +85,7 @@ public GroupVM(@NotNull Group rawObj) { this.avatar = new AvatarValueModel("group." + groupId + ".avatar", rawObj.getAvatar()); this.isMember = new BooleanValueModel("group." + groupId + ".isMember", rawObj.isMember()); this.membersCount = new IntValueModel("group." + groupId + ".membersCount", rawObj.getMembersCount()); + this.isCanWriteMessage = new BooleanValueModel("group." + groupId + ".can_write", rawObj.isCanWrite()); this.members = new ValueModel<>("group." + groupId + ".members", new HashSet<>()); this.presence = new ValueModel<>("group." + groupId + ".presence", 0); @@ -143,7 +147,16 @@ public IntValueModel getMembersCount() { return membersCount; } - + /** + * Can current user write message to a group + * + * @return can write message model + */ + @NotNull + @ObjectiveCName("isCanWriteMessageModel") + public BooleanValueModel getIsCanWriteMessage() { + return isCanWriteMessage; + } /** * Get Group creator user id From e26310f632004e74c6138f7cb8f744abd22d97d7 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Wed, 13 Jul 2016 03:34:36 +0300 Subject: [PATCH 003/414] fix(scheme): Fixing missing group id from MembersDiff --- actor-sdk/sdk-api/actor.json | 16 ++++++++++++---- .../im.actor.api/models/im/actor/api/scheme.mps | 13 ++++++++++--- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/actor-sdk/sdk-api/actor.json b/actor-sdk/sdk-api/actor.json index 3cf11dee72..2c71794a35 100644 --- a/actor-sdk/sdk-api/actor.json +++ b/actor-sdk/sdk-api/actor.json @@ -7934,7 +7934,7 @@ "type": "reference", "argument": "canSendMessage", "category": "full", - "description": " Can user send messages. Default is equals isMember for Group and false for channels." + "description": " Can user send messages. Default is equals isMember for Group and false for others." }, { "type": "reference", @@ -8997,6 +8997,14 @@ } ], "attributes": [ + { + "type": { + "type": "alias", + "childType": "groupId" + }, + "id": 1, + "name": "groupId" + }, { "type": { "type": "list", @@ -9005,7 +9013,7 @@ "childType": "userId" } }, - "id": 1, + "id": 2, "name": "removedUsers" }, { @@ -9016,12 +9024,12 @@ "childType": "Member" } }, - "id": 2, + "id": 3, "name": "addedMembers" }, { "type": "int32", - "id": 3, + "id": 4, "name": "membersCount" } ] diff --git a/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps b/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps index a3b984aa9c..54d0584961 100644 --- a/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps +++ b/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps @@ -7825,8 +7825,15 @@ - + + + + + + + + @@ -7835,7 +7842,7 @@ - + @@ -7844,7 +7851,7 @@ - + From 4a04789a9b3bb1e1de3d41ac8f37e2c104d9ac1c Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Wed, 13 Jul 2016 04:40:37 +0300 Subject: [PATCH 004/414] feat(core): Implemented all new group api updates support --- .../models/im/actor/api/scheme.mps | 4 +- .../api/updates/UpdateGroupMemberDiff.java | 24 +- .../main/java/im/actor/core/entity/Group.java | 525 ++++++++++++------ .../core/modules/groups/GroupsProcessor.java | 40 +- .../modules/groups/router/GroupRouter.java | 273 ++++----- .../java/im/actor/core/viewmodel/GroupVM.java | 10 +- 6 files changed, 498 insertions(+), 378 deletions(-) diff --git a/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps b/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps index 54d0584961..1d5ac7f1a6 100644 --- a/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps +++ b/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps @@ -7245,7 +7245,7 @@ - + @@ -7260,7 +7260,7 @@ - + diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupMemberDiff.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupMemberDiff.java index 1407d373d6..b2d2a7def4 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupMemberDiff.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupMemberDiff.java @@ -22,11 +22,13 @@ public static UpdateGroupMemberDiff fromBytes(byte[] data) throws IOException { return Bser.parse(new UpdateGroupMemberDiff(), data); } + private int groupId; private List removedUsers; private List addedMembers; private int membersCount; - public UpdateGroupMemberDiff(@NotNull List removedUsers, @NotNull List addedMembers, int membersCount) { + public UpdateGroupMemberDiff(int groupId, @NotNull List removedUsers, @NotNull List addedMembers, int membersCount) { + this.groupId = groupId; this.removedUsers = removedUsers; this.addedMembers = addedMembers; this.membersCount = membersCount; @@ -36,6 +38,10 @@ public UpdateGroupMemberDiff() { } + public int getGroupId() { + return this.groupId; + } + @NotNull public List getRemovedUsers() { return this.removedUsers; @@ -52,20 +58,22 @@ public int getMembersCount() { @Override public void parse(BserValues values) throws IOException { - this.removedUsers = values.getRepeatedInt(1); + this.groupId = values.getInt(1); + this.removedUsers = values.getRepeatedInt(2); List _addedMembers = new ArrayList(); - for (int i = 0; i < values.getRepeatedCount(2); i ++) { + for (int i = 0; i < values.getRepeatedCount(3); i ++) { _addedMembers.add(new ApiMember()); } - this.addedMembers = values.getRepeatedObj(2, _addedMembers); - this.membersCount = values.getInt(3); + this.addedMembers = values.getRepeatedObj(3, _addedMembers); + this.membersCount = values.getInt(4); } @Override public void serialize(BserWriter writer) throws IOException { - writer.writeRepeatedInt(1, this.removedUsers); - writer.writeRepeatedObj(2, this.addedMembers); - writer.writeInt(3, this.membersCount); + writer.writeInt(1, this.groupId); + writer.writeRepeatedInt(2, this.removedUsers); + writer.writeRepeatedObj(3, this.addedMembers); + writer.writeInt(4, this.membersCount); } @Override diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Group.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Group.java index 6227f359d9..0811cd297e 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Group.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Group.java @@ -55,6 +55,7 @@ public class Group extends WrapperExtEntity implements K private boolean canWrite; @NotNull @Property("readonly, nonatomic") + @SuppressWarnings("NullableProblems") private GroupType groupType; // @@ -62,10 +63,10 @@ public class Group extends WrapperExtEntity implements K // @Property("readonly, nonatomic") - private int creatorId; + private int ownerId; @Nullable @Property("readonly, nonatomic") - private String theme; + private String topic; @Nullable @Property("readonly, nonatomic") private String about; @@ -73,6 +74,14 @@ public class Group extends WrapperExtEntity implements K @Property("readonly, nonatomic") @SuppressWarnings("NullableProblems") private List members; + @Property("readonly, nonatomic") + private boolean isAsyncMembers; + @Property("readonly, nonatomic") + private boolean isCanInviteMembers; + @Property("readonly, nonatomic") + private boolean isCanViewMembers; + @Property("readonly, nonatomic") + private boolean isSharedHistory; @Property("readonly, nonatomic") private boolean haveExtension; @@ -146,8 +155,8 @@ public List getMembers() { } @Nullable - public String getTheme() { - return theme; + public String getTopic() { + return topic; } @Nullable @@ -155,8 +164,24 @@ public String getAbout() { return about; } - public int getCreatorId() { - return creatorId; + public int getOwnerId() { + return ownerId; + } + + public boolean isAsyncMembers() { + return isAsyncMembers; + } + + public boolean isCanInviteMembers() { + return isCanInviteMembers; + } + + public boolean isCanViewMembers() { + return isCanViewMembers; + } + + public boolean isSharedHistory() { + return isSharedHistory; } public boolean isHaveExtension() { @@ -256,6 +281,102 @@ public Group editCanWrite(boolean canWrite) { return new Group(res, getWrappedExt()); } + // + // Members + // + + public Group editMembers(List members) { + ApiGroupFull fullExt = null; + if (getWrappedExt() != null) { + ApiGroupFull e = getWrappedExt(); + fullExt = new ApiGroupFull(e.getId(), + e.getCreateDate(), + e.getOwnerUid(), + members, + e.getTheme(), + e.getAbout(), + e.getExt(), + e.isAsyncMembers(), + e.canViewMembers(), + e.canInvitePeople(), + e.isSharedHistory()); + fullExt.setUnmappedObjects(e.getUnmappedObjects()); + } + + ApiGroup w = getWrapped(); + ApiGroup res = new ApiGroup( + w.getId(), + w.getAccessHash(), + w.getTitle(), + w.getAvatar(), + members.size(), + w.isMember(), + w.isHidden(), + w.getGroupType(), + w.canSendMessage(), + w.getExt()); + + return new Group(res, fullExt); + } + + public Group editMembers(List added, List removed, int count) { + ApiGroupFull fullExt = null; + if (getWrappedExt() != null) { + ApiGroupFull e = getWrappedExt(); + + ArrayList nMembers = new ArrayList<>(e.getMembers()); + + // Remove members + for (Integer i : removed) { + for (ApiMember m : nMembers) { + if (m.getUid() == i) { + nMembers.remove(m); + break; + } + } + } + // Adding members + outer: + for (ApiMember a : added) { + for (ApiMember m : nMembers) { + if (m.getUid() == a.getUid()) { + nMembers.remove(m); + continue outer; + } + } + nMembers.add(a); + } + + fullExt = new ApiGroupFull(e.getId(), + e.getCreateDate(), + e.getOwnerUid(), + nMembers, + e.getTheme(), + e.getAbout(), + e.getExt(), + e.isAsyncMembers(), + e.canViewMembers(), + e.canInvitePeople(), + e.isSharedHistory()); + fullExt.setUnmappedObjects(e.getUnmappedObjects()); + } + + ApiGroup w = getWrapped(); + ApiGroup res = new ApiGroup( + w.getId(), + w.getAccessHash(), + w.getTitle(), + w.getAvatar(), + count, + w.isMember(), + w.isHidden(), + w.getGroupType(), + w.canSendMessage(), + w.getExt()); + + return new Group(res, fullExt); + } + public Group editMembersCount(int membersCount) { ApiGroup w = getWrapped(); ApiGroup res = new ApiGroup( @@ -273,175 +394,214 @@ public Group editMembersCount(int membersCount) { return new Group(res, getWrappedExt()); } + public Group editMembersBecameAsync() { + if (getWrappedExt() != null) { + ApiGroupFull e = getWrappedExt(); + ApiGroupFull fullExt = new ApiGroupFull(e.getId(), + e.getCreateDate(), + e.getOwnerUid(), + new ArrayList<>(), + e.getTheme(), + e.getAbout(), + e.getExt(), + true, + e.canViewMembers(), + e.canInvitePeople(), + e.isSharedHistory()); + fullExt.setUnmappedObjects(e.getUnmappedObjects()); + + return new Group(getWrapped(), fullExt); + } else { + return this; + } + } + + public Group editMemberChangedAdmin(int uid, Boolean isAdmin) { + if (getWrappedExt() != null) { + ApiGroupFull e = getWrappedExt(); + + ArrayList nMembers = new ArrayList<>(e.getMembers()); + for (int i = 0; i < nMembers.size(); i++) { + ApiMember m = nMembers.get(i); + if (m.getUid() == uid) { + nMembers.remove(m); + nMembers.add(i, new ApiMember(m.getUid(), m.getInviterUid(), + m.getDate(), isAdmin)); + break; + } + } + + ApiGroupFull fullExt = new ApiGroupFull(e.getId(), + e.getCreateDate(), + e.getOwnerUid(), + nMembers, + e.getTheme(), + e.getAbout(), + e.getExt(), + e.isAsyncMembers(), + e.canViewMembers(), + e.canInvitePeople(), + e.isSharedHistory()); + fullExt.setUnmappedObjects(e.getUnmappedObjects()); + + return new Group(getWrapped(), fullExt); + } else { + return this; + } + } + // // Editing Ext // - public Group editTheme(String theme) { -// ApiGroup w = getWrapped(); -// ApiGroup res = new ApiGroup( -// w.getId(), -// w.getAccessHash(), -// w.getTitle(), -// w.getAvatar(), -// w.getMembersCount(), -// w.isMember(), -// w.isHidden(), -// w.getGroupType(), -// w.canSendMessage(), -// w.getExt(), -// -// w.isAdmin(), -// w.getCreatorUid(), -// w.getMembers(), -// w.getCreateDate(), -// theme, -// w.getAbout()); -// res.setUnmappedObjects(w.getUnmappedObjects()); -// return new Group(res); - return this; + public Group editTopic(String topic) { + if (getWrappedExt() != null) { + ApiGroupFull e = getWrappedExt(); + ApiGroupFull fullExt = new ApiGroupFull(e.getId(), + e.getCreateDate(), + e.getOwnerUid(), + e.getMembers(), + topic, + e.getAbout(), + e.getExt(), + e.isAsyncMembers(), + e.canViewMembers(), + e.canInvitePeople(), + e.isSharedHistory()); + fullExt.setUnmappedObjects(e.getUnmappedObjects()); + return new Group(getWrapped(), fullExt); + } else { + return this; + } } public Group editAbout(String about) { -// ApiGroup w = getWrapped(); -// ApiGroup res = new ApiGroup( -// w.getId(), -// w.getAccessHash(), -// w.getTitle(), -// w.getAvatar(), -// w.getMembersCount(), -// w.isMember(), -// w.isHidden(), -// w.getGroupType(), -// w.canSendMessage(), -// w.getExt(), -// -// w.isAdmin(), -// w.getCreatorUid(), -// w.getMembers(), -// w.getCreateDate(), -// w.getTheme(), -// about); -// res.setUnmappedObjects(w.getUnmappedObjects()); -// return new Group(res); - return this; - } - - - public Group clearMembers() { -// ApiGroup w = getWrapped(); -// ApiGroup res = new ApiGroup( -// w.getId(), -// w.getAccessHash(), -// w.getTitle(), -// w.getAvatar(), -// w.getMembersCount(), -// w.isMember(), -// w.isHidden(), -// w.getGroupType(), -// w.canSendMessage(), -// w.getExt(), -// -// w.isAdmin(), -// w.getCreatorUid(), -// new ArrayList<>(), -// w.getCreateDate(), -// w.getTheme(), -// w.getAbout()); -// res.setUnmappedObjects(w.getUnmappedObjects()); -// return new Group(res); - return this; - } - - public Group removeMember(int uid) { -// ApiGroup w = getWrapped(); -// ArrayList nMembers = new ArrayList<>(); -// for (ApiMember member : w.getMembers()) { -// if (member.getUid() != uid) { -// nMembers.add(member); -// } -// } -// -// ApiGroup res = new ApiGroup( -// w.getId(), -// w.getAccessHash(), -// w.getTitle(), -// w.getAvatar(), -// w.getMembersCount(), -// w.isMember(), -// w.isHidden(), -// w.getGroupType(), -// w.canSendMessage(), -// w.getExt(), -// -// w.isAdmin(), -// w.getCreatorUid(), -// nMembers, -// w.getCreateDate(), -// w.getTheme(), -// w.getAbout()); -// res.setUnmappedObjects(w.getUnmappedObjects()); -// return new Group(res); - return this; - } - - public Group addMember(int uid, int inviterUid, long inviteDate) { -// ApiGroup w = getWrapped(); -// ArrayList nMembers = new ArrayList<>(); -// for (ApiMember member : w.getMembers()) { -// if (member.getUid() != uid) { -// nMembers.add(member); -// } -// } -// nMembers.add(new ApiMember(uid, inviterUid, inviteDate, null)); -// ApiGroup res = new ApiGroup( -// w.getId(), -// w.getAccessHash(), -// w.getTitle(), -// w.getAvatar(), -// w.getMembersCount(), -// w.isMember(), -// w.isHidden(), -// w.getGroupType(), -// w.canSendMessage(), -// w.getExt(), -// -// w.isAdmin(), -// w.getCreatorUid(), -// nMembers, -// w.getCreateDate(), -// w.getTheme(), -// w.getAbout()); -// res.setUnmappedObjects(w.getUnmappedObjects()); -// return new Group(res); - return this; - } - - public Group updateMembers(List nMembers) { -// ApiGroup w = getWrapped(); -// ApiGroup res = new ApiGroup( -// w.getId(), -// w.getAccessHash(), -// w.getTitle(), -// w.getAvatar(), -// w.getMembersCount(), -// w.isMember(), -// w.isHidden(), -// w.getGroupType(), -// w.canSendMessage(), -// w.getExt(), -// -// w.isAdmin(), -// w.getCreatorUid(), -// nMembers, -// w.getCreateDate(), -// w.getTheme(), -// w.getAbout()); -// res.setUnmappedObjects(w.getUnmappedObjects()); -// return new Group(res); - return this; + if (getWrappedExt() != null) { + ApiGroupFull e = getWrappedExt(); + ApiGroupFull fullExt = new ApiGroupFull(e.getId(), + e.getCreateDate(), + e.getOwnerUid(), + e.getMembers(), + e.getTheme(), + about, + e.getExt(), + e.isAsyncMembers(), + e.canViewMembers(), + e.canInvitePeople(), + e.isSharedHistory()); + fullExt.setUnmappedObjects(e.getUnmappedObjects()); + return new Group(getWrapped(), fullExt); + } else { + return this; + } + } + + public Group editFullExt(ApiMapValue ext) { + if (getWrappedExt() != null) { + ApiGroupFull e = getWrappedExt(); + ApiGroupFull fullExt = new ApiGroupFull(e.getId(), + e.getCreateDate(), + e.getOwnerUid(), + e.getMembers(), + e.getTheme(), + e.getAbout(), + ext, + e.isAsyncMembers(), + e.canViewMembers(), + e.canInvitePeople(), + e.isSharedHistory()); + fullExt.setUnmappedObjects(e.getUnmappedObjects()); + return new Group(getWrapped(), fullExt); + } else { + return this; + } + } + + public Group editCanViewMembers(boolean canViewMembers) { + if (getWrappedExt() != null) { + ApiGroupFull e = getWrappedExt(); + ApiGroupFull fullExt = new ApiGroupFull(e.getId(), + e.getCreateDate(), + e.getOwnerUid(), + e.getMembers(), + e.getTheme(), + e.getAbout(), + e.getExt(), + e.isAsyncMembers(), + canViewMembers, + e.canInvitePeople(), + e.isSharedHistory()); + fullExt.setUnmappedObjects(e.getUnmappedObjects()); + return new Group(getWrapped(), fullExt); + } else { + return this; + } + } + + public Group editCanInviteMembers(boolean canInviteMembers) { + if (getWrappedExt() != null) { + ApiGroupFull e = getWrappedExt(); + ApiGroupFull fullExt = new ApiGroupFull(e.getId(), + e.getCreateDate(), + e.getOwnerUid(), + e.getMembers(), + e.getTheme(), + e.getAbout(), + e.getExt(), + e.isAsyncMembers(), + e.canViewMembers(), + canInviteMembers, + e.isSharedHistory()); + fullExt.setUnmappedObjects(e.getUnmappedObjects()); + return new Group(getWrapped(), fullExt); + } else { + return this; + } } + public Group editOwner(int uid) { + if (getWrappedExt() != null) { + ApiGroupFull e = getWrappedExt(); + ApiGroupFull fullExt = new ApiGroupFull(e.getId(), + e.getCreateDate(), + uid, + e.getMembers(), + e.getTheme(), + e.getAbout(), + e.getExt(), + e.isAsyncMembers(), + e.canViewMembers(), + e.canInvitePeople(), + e.isSharedHistory()); + fullExt.setUnmappedObjects(e.getUnmappedObjects()); + return new Group(getWrapped(), fullExt); + } else { + return this; + } + } + + public Group editHistoryShared() { + if (getWrappedExt() != null) { + ApiGroupFull e = getWrappedExt(); + ApiGroupFull fullExt = new ApiGroupFull(e.getId(), + e.getCreateDate(), + e.getOwnerUid(), + e.getMembers(), + e.getTheme(), + e.getAbout(), + e.getExt(), + e.isAsyncMembers(), + e.canViewMembers(), + e.canInvitePeople(), + true); + fullExt.setUnmappedObjects(e.getUnmappedObjects()); + return new Group(getWrapped(), fullExt); + } else { + return this; + } + } + + @Override protected void applyWrapped(@NotNull ApiGroup wrapped, @Nullable ApiGroupFull ext) { @@ -477,24 +637,35 @@ protected void applyWrapped(@NotNull ApiGroup wrapped, @Nullable ApiGroupFull ex this.canWrite = this.groupType == GroupType.GROUP; } - // // Ext // if (ext != null) { - haveExtension = true; + this.haveExtension = true; + this.ownerId = ext.getOwnerUid(); + this.about = ext.getAbout(); + this.topic = ext.getTheme(); + this.isAsyncMembers = ext.isAsyncMembers() != null ? ext.isAsyncMembers() : false; + this.isCanViewMembers = ext.canViewMembers() != null ? ext.canViewMembers() : true; + this.isCanInviteMembers = ext.canViewMembers() != null ? ext.canViewMembers() : true; + this.isSharedHistory = ext.isSharedHistory() != null ? ext.isSharedHistory() : false; + this.members = new ArrayList<>(); + for (ApiMember m : ext.getMembers()) { + this.members.add(new GroupMember(m.getUid(), m.getInviterUid(), m.getDate(), + m.isAdmin() != null ? m.isAdmin() : false)); + } } else { - haveExtension = false; + this.haveExtension = false; + this.ownerId = 0; + this.about = null; + this.topic = null; + this.isAsyncMembers = false; + this.isCanViewMembers = false; + this.isCanInviteMembers = false; + this.isSharedHistory = false; + this.members = new ArrayList<>(); } - - // this.creatorId = wrapped.getCreatorUid(); - // this.members = new ArrayList<>(); - // for (ApiMember m : wrapped.getMembers()) { - //this.members.add(new GroupMember(m.getUid(), m.getInviterUid(), m.getDate(), m.isAdmin() != null ? m.isAdmin() : m.getUid() == this.creatorId)); - // } - // this.about = wrapped.getAbout(); - // this.theme = wrapped.getTheme(); } @Override diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsProcessor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsProcessor.java index 22d244e409..add3b8b6f7 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsProcessor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsProcessor.java @@ -8,10 +8,22 @@ import im.actor.core.api.updates.UpdateGroupAboutChangedObsolete; import im.actor.core.api.updates.UpdateGroupAvatarChanged; import im.actor.core.api.updates.UpdateGroupAvatarChangedObsolete; +import im.actor.core.api.updates.UpdateGroupCanInviteMembersChanged; +import im.actor.core.api.updates.UpdateGroupCanSendMessagesChanged; +import im.actor.core.api.updates.UpdateGroupCanViewMembersChanged; +import im.actor.core.api.updates.UpdateGroupExtChanged; +import im.actor.core.api.updates.UpdateGroupFullExtChanged; +import im.actor.core.api.updates.UpdateGroupHistoryShared; import im.actor.core.api.updates.UpdateGroupInvite; import im.actor.core.api.updates.UpdateGroupInviteObsolete; +import im.actor.core.api.updates.UpdateGroupMemberAdminChanged; +import im.actor.core.api.updates.UpdateGroupMemberChanged; +import im.actor.core.api.updates.UpdateGroupMemberDiff; +import im.actor.core.api.updates.UpdateGroupMembersBecameAsync; +import im.actor.core.api.updates.UpdateGroupMembersCountChanged; import im.actor.core.api.updates.UpdateGroupMembersUpdate; import im.actor.core.api.updates.UpdateGroupMembersUpdateObsolete; +import im.actor.core.api.updates.UpdateGroupOwnerChanged; import im.actor.core.api.updates.UpdateGroupTitleChanged; import im.actor.core.api.updates.UpdateGroupTitleChangedObsolete; import im.actor.core.api.updates.UpdateGroupTopicChanged; @@ -37,15 +49,25 @@ public GroupsProcessor(ModuleContext context) { @Override public Promise process(Update update) { - if (update instanceof UpdateGroupTitleChangedObsolete || - update instanceof UpdateGroupTopicChangedObsolete || - update instanceof UpdateGroupAboutChangedObsolete || - update instanceof UpdateGroupAvatarChangedObsolete || - update instanceof UpdateGroupInviteObsolete || - update instanceof UpdateGroupUserLeaveObsolete || - update instanceof UpdateGroupUserKickObsolete || - update instanceof UpdateGroupUserInvitedObsolete || - update instanceof UpdateGroupMembersUpdateObsolete) { + if (update instanceof UpdateGroupTitleChanged || + update instanceof UpdateGroupCanSendMessagesChanged || + update instanceof UpdateGroupMemberChanged || + update instanceof UpdateGroupAvatarChanged || + update instanceof UpdateGroupExtChanged || + + update instanceof UpdateGroupMembersUpdate || + update instanceof UpdateGroupMemberAdminChanged || + update instanceof UpdateGroupMemberDiff || + update instanceof UpdateGroupMembersBecameAsync || + update instanceof UpdateGroupMembersCountChanged || + + update instanceof UpdateGroupAboutChanged || + update instanceof UpdateGroupTopicChanged || + update instanceof UpdateGroupFullExtChanged || + update instanceof UpdateGroupOwnerChanged || + update instanceof UpdateGroupCanViewMembersChanged || + update instanceof UpdateGroupCanInviteMembersChanged || + update instanceof UpdateGroupHistoryShared) { return context().getGroupsModule().getRouter().onUpdate(update); } return null; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/router/GroupRouter.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/router/GroupRouter.java index 1fd3bb07d3..97750d1439 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/router/GroupRouter.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/router/GroupRouter.java @@ -12,30 +12,24 @@ import im.actor.core.api.ApiMapValue; import im.actor.core.api.ApiMember; import im.actor.core.api.rpc.RequestLoadFullGroups; -import im.actor.core.api.updates.UpdateGroupAboutChangedObsolete; +import im.actor.core.api.updates.UpdateGroupAboutChanged; import im.actor.core.api.updates.UpdateGroupAvatarChanged; +import im.actor.core.api.updates.UpdateGroupCanInviteMembersChanged; import im.actor.core.api.updates.UpdateGroupCanSendMessagesChanged; +import im.actor.core.api.updates.UpdateGroupCanViewMembersChanged; import im.actor.core.api.updates.UpdateGroupExtChanged; -import im.actor.core.api.updates.UpdateGroupInviteObsolete; +import im.actor.core.api.updates.UpdateGroupFullExtChanged; +import im.actor.core.api.updates.UpdateGroupHistoryShared; import im.actor.core.api.updates.UpdateGroupMemberAdminChanged; import im.actor.core.api.updates.UpdateGroupMemberChanged; import im.actor.core.api.updates.UpdateGroupMemberDiff; import im.actor.core.api.updates.UpdateGroupMembersBecameAsync; import im.actor.core.api.updates.UpdateGroupMembersCountChanged; import im.actor.core.api.updates.UpdateGroupMembersUpdate; -import im.actor.core.api.updates.UpdateGroupMembersUpdateObsolete; +import im.actor.core.api.updates.UpdateGroupOwnerChanged; import im.actor.core.api.updates.UpdateGroupTitleChanged; -import im.actor.core.api.updates.UpdateGroupTopicChangedObsolete; -import im.actor.core.api.updates.UpdateGroupUserInvitedObsolete; -import im.actor.core.api.updates.UpdateGroupUserKickObsolete; -import im.actor.core.api.updates.UpdateGroupUserLeaveObsolete; +import im.actor.core.api.updates.UpdateGroupTopicChanged; import im.actor.core.entity.Group; -import im.actor.core.entity.Message; -import im.actor.core.entity.MessageState; -import im.actor.core.entity.content.ServiceGroupCreated; -import im.actor.core.entity.content.ServiceGroupUserInvited; -import im.actor.core.entity.content.ServiceGroupUserKicked; -import im.actor.core.entity.content.ServiceGroupUserLeave; import im.actor.core.modules.ModuleActor; import im.actor.core.modules.ModuleContext; import im.actor.core.modules.groups.router.entity.RouterApplyGroups; @@ -69,188 +63,101 @@ public GroupRouter(ModuleContext context) { @Verified public Promise onAvatarChanged(int groupId, @Nullable ApiAvatar avatar) { - return forGroup(groupId, group -> { - Group upd = group.editAvatar(avatar); - groups().addOrUpdateItem(upd); - return onGroupDescChanged(upd); - }); + return editDescGroup(groupId, group -> group.editAvatar(avatar)); } @Verified public Promise onTitleChanged(int groupId, String title) { - return forGroup(groupId, group -> { - Group upd = group.editTitle(title); - groups().addOrUpdateItem(upd); - return onGroupDescChanged(upd); - }); + return editDescGroup(groupId, group -> group.editTitle(title)); } @Verified public Promise onCanWriteMessagesChanged(int groupId, boolean canWrite) { - return forGroup(groupId, group -> { - Group upd = group.editCanWrite(canWrite); - groups().addOrUpdateItem(upd); - return Promise.success(null); - }); + return editGroup(groupId, group -> group.editCanWrite(canWrite)); } @Verified public Promise onIsMemberChanged(int groupId, boolean isMember) { - return forGroup(groupId, group -> { - Group upd = group.editIsMember(isMember); - groups().addOrUpdateItem(upd); - return Promise.success(null); - }); + return editGroup(groupId, group -> group.editIsMember(isMember)); } @Verified public Promise onExtChanged(int groupId, ApiMapValue ext) { - return forGroup(groupId, group -> { - Group upd = group.editExt(ext); - groups().addOrUpdateItem(upd); - return Promise.success(null); - }); + return editGroup(groupId, group -> group.editExt(ext)); } // // Members Updates // - - // - // Updates Ext - // - @Verified - public Promise onGroupInvite(int groupId, long rid, int inviterId, long date, boolean isSilent) { - return forGroup(groupId, group -> { - - groups().addOrUpdateItem(group - .addMember(myUid(), inviterId, date)); - - if (!isSilent) { - if (inviterId == myUid()) { - // If current user invite himself, add create group message - Message message = new Message(rid, date, date, inviterId, - MessageState.UNKNOWN, ServiceGroupCreated.create()); - return getRouter().onNewMessage(group.peer(), message); - } else { - // else add invite message - Message message = new Message(rid, date, date, inviterId, - MessageState.SENT, ServiceGroupUserInvited.create(myUid())); - return getRouter().onNewMessage(group.peer(), message); - } - } - - return Promise.success(null); - }); + public Promise onMembersChanged(int groupId, List members) { + return editGroup(groupId, group -> group.editMembers(members)); } @Verified - public Promise onUserLeave(int groupId, long rid, int uid, long date, boolean isSilent) { - return forGroup(groupId, group -> { - - if (uid == myUid()) { - // If current user leave, clear members and change member state - groups().addOrUpdateItem(group - .clearMembers()); - } else { - // else remove leaved user - groups().addOrUpdateItem(group - .removeMember(uid)); - } - - // Create message if needed - if (!isSilent) { - Message message = new Message(rid, date, date, uid, - uid == myUid() ? MessageState.SENT : MessageState.UNKNOWN, - ServiceGroupUserLeave.create()); - return getRouter().onNewMessage(group.peer(), message); - } - - return Promise.success(null); - }); + public Promise onMembersChanged(int groupId, List added, List removed, int count) { + return editGroup(groupId, group -> group.editMembers(added, removed, count)); } @Verified - public Promise onUserKicked(int groupId, long rid, int uid, int kicker, long date, boolean isSilent) { - return forGroup(groupId, group -> { - - if (uid == myUid()) { - // If kicked me, clear members and change member state - groups().addOrUpdateItem(group - .clearMembers()); - } else { - // else remove kicked user - groups().addOrUpdateItem(group - .removeMember(uid)); - } - - // Create message if needed - if (!isSilent) { - Message message = new Message(rid, date, date, kicker, - kicker == myUid() ? MessageState.SENT : MessageState.UNKNOWN, - ServiceGroupUserKicked.create(uid)); - return getRouter().onNewMessage(group.peer(), message); - } - - return Promise.success(null); - }); + public Promise onMembersChanged(int groupId, int membersCount) { + return editGroup(groupId, group -> group.editMembersCount(membersCount)); } @Verified - public Promise onUserAdded(int groupId, long rid, int uid, int adder, long date, boolean isSilent) { - return forGroup(groupId, group -> { - - groups().addOrUpdateItem(group.addMember(uid, adder, date)); - - // Create message if needed - if (!isSilent) { - Message message = new Message(rid, date, date, adder, - adder == myUid() ? MessageState.SENT : MessageState.UNKNOWN, - ServiceGroupUserInvited.create(uid)); - return getRouter().onNewMessage(group.peer(), message); - } - return Promise.success(null); - }); + public Promise onMembersBecameAsync(int groupId) { + return editGroup(groupId, group -> group.editMembersBecameAsync()); } @Verified - public Promise onTopicChanged(int groupId, String topic) { - return forGroup(groupId, group -> { - - // Change group title - Group upd = group.editTheme(topic); + public Promise onMemberChangedAdmin(int groupId, int uid, Boolean isAdmin) { + return editGroup(groupId, group -> group.editMemberChangedAdmin(uid, isAdmin)); + } - // Update group - groups().addOrUpdateItem(upd); + // + // Updates Ext + // - // Notify about group change - return onGroupDescChanged(upd); - }); + @Verified + public Promise onTopicChanged(int groupId, String topic) { + return editGroup(groupId, group -> group.editTopic(topic)); } @Verified public Promise onAboutChanged(int groupId, String about) { - return forGroup(groupId, group -> { - - groups().addOrUpdateItem(group.editAbout(about)); + return editGroup(groupId, group -> group.editAbout(about)); + } - return Promise.success(null); - }); + @Verified + public Promise onFullExtChanged(int groupId, ApiMapValue ext) { + return editGroup(groupId, group -> group.editFullExt(ext)); } + @Verified + public Promise onEditCanViewMembers(int groupId, boolean canViewMembers) { + return editGroup(groupId, group -> group.editCanViewMembers(canViewMembers)); + } @Verified - public Promise onMembersUpdated(int groupId, List members) { - return forGroup(groupId, group -> { + public Promise onEditCanInviteMembers(int groupId, boolean canViewMembers) { + return editGroup(groupId, group -> group.editCanInviteMembers(canViewMembers)); + } - groups().addOrUpdateItem(group.updateMembers(members)); + @Verified + public Promise onOwnerChanged(int groupId, int updatedOwner) { + return editGroup(groupId, group -> group.editOwner(updatedOwner)); + } - return Promise.success(null); - }); + @Verified + public Promise onHistoryShared(int groupId) { + return editGroup(groupId, group -> group.editHistoryShared()); } + // + // Wrapper + // + private Promise forGroup(int groupId, Function> func) { freeze(); return groups().getValueAsync(groupId) @@ -266,6 +173,20 @@ private Promise forGroup(int groupId, Function> func) }); } + private Promise editGroup(int groupId, Function func) { + return forGroup(groupId, group -> { + groups().addOrUpdateItem(func.apply(group)); + return Promise.success(null); + }); + } + + private Promise editDescGroup(int groupId, Function func) { + return forGroup(groupId, group -> { + Group g = func.apply(group); + groups().addOrUpdateItem(g); + return onGroupDescChanged(g); + }); + } // // Entities @@ -327,7 +248,6 @@ private void onRequestLoadFullGroup(int gid) { .after((r, e) -> unfreeze()); } - // // Tools // @@ -350,6 +270,7 @@ private void unfreeze() { unstashAll(); } + // // Messages // @@ -359,6 +280,7 @@ private Promise onUpdate(Update update) { // // Main // + if (update instanceof UpdateGroupTitleChanged) { UpdateGroupTitleChanged titleChanged = (UpdateGroupTitleChanged) update; return onTitleChanged(titleChanged.getGroupId(), titleChanged.getTitle()); @@ -379,56 +301,53 @@ private Promise onUpdate(Update update) { // // Members // + else if (update instanceof UpdateGroupMembersUpdate) { UpdateGroupMembersUpdate membersUpdate = (UpdateGroupMembersUpdate) update; - + return onMembersChanged(membersUpdate.getGroupId(), membersUpdate.getMembers()); } else if (update instanceof UpdateGroupMemberAdminChanged) { UpdateGroupMemberAdminChanged adminChanged = (UpdateGroupMemberAdminChanged) update; - + return onMemberChangedAdmin(adminChanged.getGroupId(), adminChanged.getUserId(), + adminChanged.isAdmin()); } else if (update instanceof UpdateGroupMemberDiff) { UpdateGroupMemberDiff memberDiff = (UpdateGroupMemberDiff) update; - + return onMembersChanged(memberDiff.getGroupId(), memberDiff.getAddedMembers(), + memberDiff.getRemovedUsers(), memberDiff.getMembersCount()); } else if (update instanceof UpdateGroupMembersBecameAsync) { UpdateGroupMembersBecameAsync becameAsync = (UpdateGroupMembersBecameAsync) update; - + return onMembersBecameAsync(becameAsync.getGroupId()); } else if (update instanceof UpdateGroupMembersCountChanged) { UpdateGroupMembersCountChanged membersCountChanged = (UpdateGroupMembersCountChanged) update; - + return onMembersChanged(membersCountChanged.getGroupId(), membersCountChanged.getMembersCount()); } // // Ext // - else if (update instanceof UpdateGroupTopicChangedObsolete) { - UpdateGroupTopicChangedObsolete topicChanged = (UpdateGroupTopicChangedObsolete) update; + else if (update instanceof UpdateGroupTopicChanged) { + UpdateGroupTopicChanged topicChanged = (UpdateGroupTopicChanged) update; return onTopicChanged(topicChanged.getGroupId(), topicChanged.getTopic()); - } else if (update instanceof UpdateGroupAboutChangedObsolete) { - UpdateGroupAboutChangedObsolete aboutChanged = (UpdateGroupAboutChangedObsolete) update; + } else if (update instanceof UpdateGroupAboutChanged) { + UpdateGroupAboutChanged aboutChanged = (UpdateGroupAboutChanged) update; return onAboutChanged(aboutChanged.getGroupId(), aboutChanged.getAbout()); - } else if (update instanceof UpdateGroupInviteObsolete) { - UpdateGroupInviteObsolete groupInvite = (UpdateGroupInviteObsolete) update; - return onGroupInvite(groupInvite.getGroupId(), - groupInvite.getRid(), groupInvite.getInviteUid(), groupInvite.getDate(), - false); - } else if (update instanceof UpdateGroupUserLeaveObsolete) { - UpdateGroupUserLeaveObsolete leave = (UpdateGroupUserLeaveObsolete) update; - return onUserLeave(leave.getGroupId(), leave.getRid(), leave.getUid(), - leave.getDate(), false); - } else if (update instanceof UpdateGroupUserKickObsolete) { - UpdateGroupUserKickObsolete userKick = (UpdateGroupUserKickObsolete) update; - return onUserKicked(userKick.getGroupId(), - userKick.getRid(), userKick.getUid(), userKick.getKickerUid(), userKick.getDate(), - false); - } else if (update instanceof UpdateGroupUserInvitedObsolete) { - UpdateGroupUserInvitedObsolete userInvited = (UpdateGroupUserInvitedObsolete) update; - return onUserAdded(userInvited.getGroupId(), - userInvited.getRid(), userInvited.getUid(), userInvited.getInviterUid(), userInvited.getDate(), - false); - } else if (update instanceof UpdateGroupMembersUpdateObsolete) { - return onMembersUpdated(((UpdateGroupMembersUpdateObsolete) update).getGroupId(), - ((UpdateGroupMembersUpdateObsolete) update).getMembers()); + } else if (update instanceof UpdateGroupHistoryShared) { + UpdateGroupHistoryShared historyShared = (UpdateGroupHistoryShared) update; + return onHistoryShared(historyShared.getGroupId()); + } else if (update instanceof UpdateGroupOwnerChanged) { + UpdateGroupOwnerChanged ownerChanged = (UpdateGroupOwnerChanged) update; + return onOwnerChanged(ownerChanged.getGroupId(), ownerChanged.getUserId()); + } else if (update instanceof UpdateGroupCanViewMembersChanged) { + UpdateGroupCanViewMembersChanged membersChanged = (UpdateGroupCanViewMembersChanged) update; + return onEditCanViewMembers(membersChanged.getGroupId(), membersChanged.canViewMembers()); + } else if (update instanceof UpdateGroupCanInviteMembersChanged) { + UpdateGroupCanInviteMembersChanged changed = (UpdateGroupCanInviteMembersChanged) update; + return onEditCanInviteMembers(changed.getGroupId(), changed.canInviteMembers()); + } else if (update instanceof UpdateGroupFullExtChanged) { + UpdateGroupFullExtChanged extChanged = (UpdateGroupFullExtChanged) update; + return onFullExtChanged(extChanged.getGroupId(), extChanged.getExt()); } + return Promise.success(null); } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java index 758101928a..3b66955cf0 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java @@ -80,16 +80,16 @@ public class GroupVM extends BaseValueModel { public GroupVM(@NotNull Group rawObj) { super(rawObj); this.groupId = rawObj.getGroupId(); - this.creatorId = rawObj.getCreatorId(); + this.creatorId = rawObj.getOwnerId(); this.name = new StringValueModel("group." + groupId + ".title", rawObj.getTitle()); this.avatar = new AvatarValueModel("group." + groupId + ".avatar", rawObj.getAvatar()); this.isMember = new BooleanValueModel("group." + groupId + ".isMember", rawObj.isMember()); this.membersCount = new IntValueModel("group." + groupId + ".membersCount", rawObj.getMembersCount()); this.isCanWriteMessage = new BooleanValueModel("group." + groupId + ".can_write", rawObj.isCanWrite()); - this.members = new ValueModel<>("group." + groupId + ".members", new HashSet<>()); + this.members = new ValueModel<>("group." + groupId + ".members", new HashSet<>(rawObj.getMembers())); this.presence = new ValueModel<>("group." + groupId + ".presence", 0); - this.theme = new StringValueModel("group." + groupId + ".theme", rawObj.getTheme()); + this.theme = new StringValueModel("group." + groupId + ".theme", rawObj.getTopic()); this.about = new StringValueModel("group." + groupId + ".about", rawObj.getAbout()); } @@ -197,9 +197,9 @@ protected void updateValues(@NotNull Group rawObj) { isChanged |= membersCount.change(rawObj.getMembersCount()); isChanged |= isMember.change(rawObj.isMember()); - isChanged |= theme.change(rawObj.getTheme()); + isChanged |= theme.change(rawObj.getTopic()); isChanged |= about.change(rawObj.getAbout()); - isChanged |= members.change(new HashSet<>()); + isChanged |= members.change(new HashSet<>(rawObj.getMembers())); if (isChanged) { notifyChange(); From 49379949a4ca13b0ca88228fe7d23c27c5883ca4 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Wed, 13 Jul 2016 05:13:05 +0300 Subject: [PATCH 005/414] fix(scheme): Fix missing group creation date in response --- actor-sdk/sdk-api/actor.json | 18 ++++++++++++++++-- .../models/im/actor/api/scheme.mps | 12 ++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/actor-sdk/sdk-api/actor.json b/actor-sdk/sdk-api/actor.json index 2c71794a35..0b3f29507d 100644 --- a/actor-sdk/sdk-api/actor.json +++ b/actor-sdk/sdk-api/actor.json @@ -8170,7 +8170,7 @@ "type": "reference", "argument": "isAsyncMembers", "category": "full", - "description": " Is Members need to be loaded asynchronous." + "description": " Is Members need to be loaded asynchronous. Default is false." }, { "type": "reference", @@ -8188,7 +8188,7 @@ "type": "reference", "argument": "isSharedHistory", "category": "full", - "description": " Is history shared among all users." + "description": " Is history shared among all users. Default is false." } ], "attributes": [ @@ -9166,6 +9166,12 @@ "argument": "userPeers", "category": "full", "description": " Referenced users" + }, + { + "type": "reference", + "argument": "date", + "category": "full", + "description": " Group creation date" } ], "attributes": [ @@ -9182,6 +9188,14 @@ "id": 2, "name": "state" }, + { + "type": { + "type": "alias", + "childType": "date" + }, + "id": 6, + "name": "date" + }, { "type": { "type": "struct", diff --git a/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps b/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps index 1d5ac7f1a6..71bfaf3c3a 100644 --- a/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps +++ b/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps @@ -8011,6 +8011,13 @@ + + + + + + + @@ -8067,6 +8074,11 @@ + + + + + From c07fdef10e05216513bee02c4d2963724ee076f4 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Wed, 13 Jul 2016 05:14:08 +0300 Subject: [PATCH 006/414] wip(core): Improve update handling for group v2 api --- .../controllers/group/GroupInfoFragment.java | 2 +- .../core/modules/groups/GroupsModule.java | 14 ++++-- .../sequence/processor/UpdateValidator.java | 47 ++++++++++++++++++- 3 files changed, 58 insertions(+), 5 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java index 91337b195f..e97638cbf7 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java @@ -211,7 +211,7 @@ public void onClick(View v) { //Members TextView memberCount = (TextView) header.findViewById(R.id.membersCount); - memberCount.setText(groupInfo.getMembersCount() + ""); + memberCount.setText(groupInfo.getMembersCount().get() + ""); memberCount.setTextColor(style.getTextHintColor()); listView.addHeaderView(header, null, false); diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsModule.java index 6221c5dd8d..2c817b811c 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsModule.java @@ -15,6 +15,7 @@ import im.actor.core.api.ApiOutPeer; import im.actor.core.api.ApiPeer; import im.actor.core.api.ApiPeerType; +import im.actor.core.api.ApiServiceExGroupCreated; import im.actor.core.api.ApiServiceExUserJoined; import im.actor.core.api.ApiServiceMessage; import im.actor.core.api.ApiUserOutPeer; @@ -133,6 +134,7 @@ public GroupRouterInt getRouter() { // public Promise createGroup(final String title, final String avatarDescriptor, final int[] uids) { + long rid = RandomUtils.nextRid(); return Promise.success(uids) .map((Function>) ints -> { ArrayList peers = new ArrayList<>(); @@ -145,9 +147,15 @@ public Promise createGroup(final String title, final String avatarDescr return peers; }) .flatMap(apiUserOutPeers -> - api(new RequestCreateGroup(RandomUtils.nextRid(), title, apiUserOutPeers, null, ApiSupportConfiguration.OPTIMIZATIONS))) - .chain(response -> updates().applyRelatedData(response.getUsers(), response.getGroup())) - .map(responseCreateGroup -> responseCreateGroup.getGroup().getId()) + api(new RequestCreateGroup(rid, title, apiUserOutPeers, + null, ApiSupportConfiguration.OPTIMIZATIONS))) + .chain(r -> updates().applyRelatedData(r.getUsers(), r.getGroup())) + .chain(r -> updates().applyUpdate(r.getSeq(), r.getState(), + new UpdateMessage( + new ApiPeer(ApiPeerType.GROUP, r.getGroup().getId()), myUid(), 0, rid, + new ApiServiceMessage("Group created", new ApiServiceExGroupCreated()), + null, null))) + .map(r -> r.getGroup().getId()) .then(integer -> { if (avatarDescriptor != null) { changeAvatar(integer, avatarDescriptor); diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/sequence/processor/UpdateValidator.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/sequence/processor/UpdateValidator.java index 541e07e713..430d13d96e 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/sequence/processor/UpdateValidator.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/sequence/processor/UpdateValidator.java @@ -5,12 +5,21 @@ import im.actor.core.api.ApiDialogGroup; import im.actor.core.api.ApiDialogShort; +import im.actor.core.api.ApiMember; import im.actor.core.api.ApiPeerType; import im.actor.core.api.updates.UpdateChatGroupsChanged; import im.actor.core.api.updates.UpdateContactRegistered; import im.actor.core.api.updates.UpdateContactsAdded; import im.actor.core.api.updates.UpdateContactsRemoved; +import im.actor.core.api.updates.UpdateGroupExtChanged; +import im.actor.core.api.updates.UpdateGroupFullExtChanged; import im.actor.core.api.updates.UpdateGroupInvite; +import im.actor.core.api.updates.UpdateGroupMemberAdminChanged; +import im.actor.core.api.updates.UpdateGroupMemberChanged; +import im.actor.core.api.updates.UpdateGroupMemberDiff; +import im.actor.core.api.updates.UpdateGroupMembersCountChanged; +import im.actor.core.api.updates.UpdateGroupMembersUpdate; +import im.actor.core.api.updates.UpdateGroupOwnerChanged; import im.actor.core.api.updates.UpdateGroupUserInvited; import im.actor.core.api.updates.UpdateGroupUserKick; import im.actor.core.api.updates.UpdateGroupUserLeave; @@ -80,8 +89,44 @@ public boolean isCausesInvalidation(Update update) { } } } + } else if (update instanceof UpdateGroupMemberChanged) { + UpdateGroupMemberChanged memberChanged = (UpdateGroupMemberChanged) update; + groups.add(memberChanged.getGroupId()); + } else if (update instanceof UpdateGroupMemberDiff) { + UpdateGroupMemberDiff diff = (UpdateGroupMemberDiff) update; + groups.add(diff.getGroupId()); + for (Integer u : diff.getRemovedUsers()) { + users.add(u); + } + for (ApiMember m : diff.getAddedMembers()) { + users.add(m.getInviterUid()); + users.add(m.getUid()); + } + } else if (update instanceof UpdateGroupMembersUpdate) { + UpdateGroupMembersUpdate u = (UpdateGroupMembersUpdate) update; + groups.add(u.getGroupId()); + for (ApiMember m : u.getMembers()) { + users.add(m.getInviterUid()); + users.add(m.getUid()); + } + } else if (update instanceof UpdateGroupMemberAdminChanged) { + UpdateGroupMemberAdminChanged u = (UpdateGroupMemberAdminChanged) update; + users.add(u.getUserId()); + groups.add(u.getGroupId()); + } else if (update instanceof UpdateGroupMembersCountChanged) { + UpdateGroupMembersCountChanged countChanged = (UpdateGroupMembersCountChanged) update; + groups.add(countChanged.getGroupId()); + } else if (update instanceof UpdateGroupOwnerChanged) { + UpdateGroupOwnerChanged ownerChanged = (UpdateGroupOwnerChanged) update; + groups.add(ownerChanged.getGroupId()); + users.add(ownerChanged.getUserId()); + } else if (update instanceof UpdateGroupFullExtChanged) { + UpdateGroupFullExtChanged fullExtChanged = (UpdateGroupFullExtChanged) update; + groups.add(fullExtChanged.getGroupId()); + } else if (update instanceof UpdateGroupExtChanged) { + UpdateGroupExtChanged extChanged = (UpdateGroupExtChanged) update; + groups.add(extChanged.getGroupId()); } - if (!hasUsers(users)) { return true; } From 1cd86f60ea50aaaf98e1150f0dc3408cf78b9629 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Wed, 13 Jul 2016 06:17:27 +0300 Subject: [PATCH 007/414] feat(core): Improve group update processing --- .../actor/core/api/rpc/RequestEnterGroup.java | 65 ---------- .../core/api/rpc/RequestUnregisterPush.java | 49 -------- .../core/api/rpc/ResponseCreateGroup.java | 10 +- .../core/api/rpc/ResponseEnterGroup.java | 113 ------------------ .../core/api/rpc/ResponseMakeUserAdmin.java | 85 ------------- .../core/api/updates/UpdateGroupInvite.java | 88 -------------- .../api/updates/UpdateGroupMembersUpdate.java | 75 ------------ .../api/updates/UpdateGroupUserInvited.java | 97 --------------- .../core/api/updates/UpdateGroupUserKick.java | 97 --------------- .../api/updates/UpdateGroupUserLeave.java | 88 -------------- .../core/modules/groups/GroupsModule.java | 112 ++--------------- .../core/modules/groups/GroupsProcessor.java | 17 +-- .../modules/groups/router/GroupRouter.java | 6 +- .../core/modules/sequence/SequenceActor.java | 13 +- .../actor/core/modules/sequence/Updates.java | 10 +- .../sequence/processor/UpdateValidator.java | 28 +---- 16 files changed, 42 insertions(+), 911 deletions(-) delete mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/RequestEnterGroup.java delete mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/RequestUnregisterPush.java delete mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/ResponseEnterGroup.java delete mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/ResponseMakeUserAdmin.java delete mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupInvite.java delete mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupMembersUpdate.java delete mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupUserInvited.java delete mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupUserKick.java delete mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupUserLeave.java diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/RequestEnterGroup.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/RequestEnterGroup.java deleted file mode 100644 index 136c20261f..0000000000 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/RequestEnterGroup.java +++ /dev/null @@ -1,65 +0,0 @@ -package im.actor.core.api.rpc; -/* - * Generated by the Actor API Scheme generator. DO NOT EDIT! - */ - -import im.actor.runtime.bser.*; -import im.actor.runtime.collections.*; -import static im.actor.runtime.bser.Utils.*; -import im.actor.core.network.parser.*; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.NotNull; -import com.google.j2objc.annotations.ObjectiveCName; -import java.io.IOException; -import java.util.List; -import java.util.ArrayList; -import im.actor.core.api.*; - -public class RequestEnterGroup extends Request { - - public static final int HEADER = 0xc7; - public static RequestEnterGroup fromBytes(byte[] data) throws IOException { - return Bser.parse(new RequestEnterGroup(), data); - } - - private ApiGroupOutPeer peer; - - public RequestEnterGroup(@NotNull ApiGroupOutPeer peer) { - this.peer = peer; - } - - public RequestEnterGroup() { - - } - - @NotNull - public ApiGroupOutPeer getPeer() { - return this.peer; - } - - @Override - public void parse(BserValues values) throws IOException { - this.peer = values.getObj(1, new ApiGroupOutPeer()); - } - - @Override - public void serialize(BserWriter writer) throws IOException { - if (this.peer == null) { - throw new IOException(); - } - writer.writeObject(1, this.peer); - } - - @Override - public String toString() { - String res = "rpc EnterGroup{"; - res += "peer=" + this.peer; - res += "}"; - return res; - } - - @Override - public int getHeaderKey() { - return HEADER; - } -} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/RequestUnregisterPush.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/RequestUnregisterPush.java deleted file mode 100644 index 29f0c521a0..0000000000 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/RequestUnregisterPush.java +++ /dev/null @@ -1,49 +0,0 @@ -package im.actor.core.api.rpc; -/* - * Generated by the Actor API Scheme generator. DO NOT EDIT! - */ - -import im.actor.runtime.bser.*; -import im.actor.runtime.collections.*; -import static im.actor.runtime.bser.Utils.*; -import im.actor.core.network.parser.*; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.NotNull; -import com.google.j2objc.annotations.ObjectiveCName; -import java.io.IOException; -import java.util.List; -import java.util.ArrayList; -import im.actor.core.api.*; - -public class RequestUnregisterPush extends Request { - - public static final int HEADER = 0x34; - public static RequestUnregisterPush fromBytes(byte[] data) throws IOException { - return Bser.parse(new RequestUnregisterPush(), data); - } - - - public RequestUnregisterPush() { - - } - - @Override - public void parse(BserValues values) throws IOException { - } - - @Override - public void serialize(BserWriter writer) throws IOException { - } - - @Override - public String toString() { - String res = "rpc UnregisterPush{"; - res += "}"; - return res; - } - - @Override - public int getHeaderKey() { - return HEADER; - } -} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/ResponseCreateGroup.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/ResponseCreateGroup.java index d91f7aeeb8..ab181a6f7c 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/ResponseCreateGroup.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/ResponseCreateGroup.java @@ -24,13 +24,15 @@ public static ResponseCreateGroup fromBytes(byte[] data) throws IOException { private int seq; private byte[] state; + private long date; private ApiGroup group; private List users; private List userPeers; - public ResponseCreateGroup(int seq, @NotNull byte[] state, @NotNull ApiGroup group, @NotNull List users, @NotNull List userPeers) { + public ResponseCreateGroup(int seq, @NotNull byte[] state, long date, @NotNull ApiGroup group, @NotNull List users, @NotNull List userPeers) { this.seq = seq; this.state = state; + this.date = date; this.group = group; this.users = users; this.userPeers = userPeers; @@ -49,6 +51,10 @@ public byte[] getState() { return this.state; } + public long getDate() { + return this.date; + } + @NotNull public ApiGroup getGroup() { return this.group; @@ -68,6 +74,7 @@ public List getUserPeers() { public void parse(BserValues values) throws IOException { this.seq = values.getInt(1); this.state = values.getBytes(2); + this.date = values.getLong(6); this.group = values.getObj(3, new ApiGroup()); List _users = new ArrayList(); for (int i = 0; i < values.getRepeatedCount(4); i ++) { @@ -88,6 +95,7 @@ public void serialize(BserWriter writer) throws IOException { throw new IOException(); } writer.writeBytes(2, this.state); + writer.writeLong(6, this.date); if (this.group == null) { throw new IOException(); } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/ResponseEnterGroup.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/ResponseEnterGroup.java deleted file mode 100644 index c5278e80dc..0000000000 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/ResponseEnterGroup.java +++ /dev/null @@ -1,113 +0,0 @@ -package im.actor.core.api.rpc; -/* - * Generated by the Actor API Scheme generator. DO NOT EDIT! - */ - -import im.actor.runtime.bser.*; -import im.actor.runtime.collections.*; -import static im.actor.runtime.bser.Utils.*; -import im.actor.core.network.parser.*; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.NotNull; -import com.google.j2objc.annotations.ObjectiveCName; -import java.io.IOException; -import java.util.List; -import java.util.ArrayList; -import im.actor.core.api.*; - -public class ResponseEnterGroup extends Response { - - public static final int HEADER = 0xc8; - public static ResponseEnterGroup fromBytes(byte[] data) throws IOException { - return Bser.parse(new ResponseEnterGroup(), data); - } - - private ApiGroup group; - private List users; - private long rid; - private int seq; - private byte[] state; - private long date; - - public ResponseEnterGroup(@NotNull ApiGroup group, @NotNull List users, long rid, int seq, @NotNull byte[] state, long date) { - this.group = group; - this.users = users; - this.rid = rid; - this.seq = seq; - this.state = state; - this.date = date; - } - - public ResponseEnterGroup() { - - } - - @NotNull - public ApiGroup getGroup() { - return this.group; - } - - @NotNull - public List getUsers() { - return this.users; - } - - public long getRid() { - return this.rid; - } - - public int getSeq() { - return this.seq; - } - - @NotNull - public byte[] getState() { - return this.state; - } - - public long getDate() { - return this.date; - } - - @Override - public void parse(BserValues values) throws IOException { - this.group = values.getObj(1, new ApiGroup()); - List _users = new ArrayList(); - for (int i = 0; i < values.getRepeatedCount(2); i ++) { - _users.add(new ApiUser()); - } - this.users = values.getRepeatedObj(2, _users); - this.rid = values.getLong(3); - this.seq = values.getInt(4); - this.state = values.getBytes(5); - this.date = values.getLong(6); - } - - @Override - public void serialize(BserWriter writer) throws IOException { - if (this.group == null) { - throw new IOException(); - } - writer.writeObject(1, this.group); - writer.writeRepeatedObj(2, this.users); - writer.writeLong(3, this.rid); - writer.writeInt(4, this.seq); - if (this.state == null) { - throw new IOException(); - } - writer.writeBytes(5, this.state); - writer.writeLong(6, this.date); - } - - @Override - public String toString() { - String res = "tuple EnterGroup{"; - res += "}"; - return res; - } - - @Override - public int getHeaderKey() { - return HEADER; - } -} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/ResponseMakeUserAdmin.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/ResponseMakeUserAdmin.java deleted file mode 100644 index f1d275bc68..0000000000 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/ResponseMakeUserAdmin.java +++ /dev/null @@ -1,85 +0,0 @@ -package im.actor.core.api.rpc; -/* - * Generated by the Actor API Scheme generator. DO NOT EDIT! - */ - -import im.actor.runtime.bser.*; -import im.actor.runtime.collections.*; -import static im.actor.runtime.bser.Utils.*; -import im.actor.core.network.parser.*; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.NotNull; -import com.google.j2objc.annotations.ObjectiveCName; -import java.io.IOException; -import java.util.List; -import java.util.ArrayList; -import im.actor.core.api.*; - -public class ResponseMakeUserAdmin extends Response { - - public static final int HEADER = 0xd7; - public static ResponseMakeUserAdmin fromBytes(byte[] data) throws IOException { - return Bser.parse(new ResponseMakeUserAdmin(), data); - } - - private List members; - private int seq; - private byte[] state; - - public ResponseMakeUserAdmin(@NotNull List members, int seq, @NotNull byte[] state) { - this.members = members; - this.seq = seq; - this.state = state; - } - - public ResponseMakeUserAdmin() { - - } - - @NotNull - public List getMembers() { - return this.members; - } - - public int getSeq() { - return this.seq; - } - - @NotNull - public byte[] getState() { - return this.state; - } - - @Override - public void parse(BserValues values) throws IOException { - List _members = new ArrayList(); - for (int i = 0; i < values.getRepeatedCount(1); i ++) { - _members.add(new ApiMember()); - } - this.members = values.getRepeatedObj(1, _members); - this.seq = values.getInt(2); - this.state = values.getBytes(3); - } - - @Override - public void serialize(BserWriter writer) throws IOException { - writer.writeRepeatedObj(1, this.members); - writer.writeInt(2, this.seq); - if (this.state == null) { - throw new IOException(); - } - writer.writeBytes(3, this.state); - } - - @Override - public String toString() { - String res = "tuple MakeUserAdmin{"; - res += "}"; - return res; - } - - @Override - public int getHeaderKey() { - return HEADER; - } -} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupInvite.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupInvite.java deleted file mode 100644 index 108bc22396..0000000000 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupInvite.java +++ /dev/null @@ -1,88 +0,0 @@ -package im.actor.core.api.updates; -/* - * Generated by the Actor API Scheme generator. DO NOT EDIT! - */ - -import im.actor.runtime.bser.*; -import im.actor.runtime.collections.*; -import static im.actor.runtime.bser.Utils.*; -import im.actor.core.network.parser.*; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.NotNull; -import com.google.j2objc.annotations.ObjectiveCName; -import java.io.IOException; -import java.util.List; -import java.util.ArrayList; -import im.actor.core.api.*; - -public class UpdateGroupInvite extends Update { - - public static final int HEADER = 0x24; - public static UpdateGroupInvite fromBytes(byte[] data) throws IOException { - return Bser.parse(new UpdateGroupInvite(), data); - } - - private int groupId; - private long rid; - private int inviteUid; - private long date; - - public UpdateGroupInvite(int groupId, long rid, int inviteUid, long date) { - this.groupId = groupId; - this.rid = rid; - this.inviteUid = inviteUid; - this.date = date; - } - - public UpdateGroupInvite() { - - } - - public int getGroupId() { - return this.groupId; - } - - public long getRid() { - return this.rid; - } - - public int getInviteUid() { - return this.inviteUid; - } - - public long getDate() { - return this.date; - } - - @Override - public void parse(BserValues values) throws IOException { - this.groupId = values.getInt(1); - this.rid = values.getLong(9); - this.inviteUid = values.getInt(5); - this.date = values.getLong(8); - } - - @Override - public void serialize(BserWriter writer) throws IOException { - writer.writeInt(1, this.groupId); - writer.writeLong(9, this.rid); - writer.writeInt(5, this.inviteUid); - writer.writeLong(8, this.date); - } - - @Override - public String toString() { - String res = "update GroupInvite{"; - res += "groupId=" + this.groupId; - res += ", rid=" + this.rid; - res += ", inviteUid=" + this.inviteUid; - res += ", date=" + this.date; - res += "}"; - return res; - } - - @Override - public int getHeaderKey() { - return HEADER; - } -} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupMembersUpdate.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupMembersUpdate.java deleted file mode 100644 index 29735d556d..0000000000 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupMembersUpdate.java +++ /dev/null @@ -1,75 +0,0 @@ -package im.actor.core.api.updates; -/* - * Generated by the Actor API Scheme generator. DO NOT EDIT! - */ - -import im.actor.runtime.bser.*; -import im.actor.runtime.collections.*; -import static im.actor.runtime.bser.Utils.*; -import im.actor.core.network.parser.*; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.NotNull; -import com.google.j2objc.annotations.ObjectiveCName; -import java.io.IOException; -import java.util.List; -import java.util.ArrayList; -import im.actor.core.api.*; - -public class UpdateGroupMembersUpdate extends Update { - - public static final int HEADER = 0x2c; - public static UpdateGroupMembersUpdate fromBytes(byte[] data) throws IOException { - return Bser.parse(new UpdateGroupMembersUpdate(), data); - } - - private int groupId; - private List members; - - public UpdateGroupMembersUpdate(int groupId, @NotNull List members) { - this.groupId = groupId; - this.members = members; - } - - public UpdateGroupMembersUpdate() { - - } - - public int getGroupId() { - return this.groupId; - } - - @NotNull - public List getMembers() { - return this.members; - } - - @Override - public void parse(BserValues values) throws IOException { - this.groupId = values.getInt(1); - List _members = new ArrayList(); - for (int i = 0; i < values.getRepeatedCount(2); i ++) { - _members.add(new ApiMember()); - } - this.members = values.getRepeatedObj(2, _members); - } - - @Override - public void serialize(BserWriter writer) throws IOException { - writer.writeInt(1, this.groupId); - writer.writeRepeatedObj(2, this.members); - } - - @Override - public String toString() { - String res = "update GroupMembersUpdate{"; - res += "groupId=" + this.groupId; - res += ", members=" + this.members; - res += "}"; - return res; - } - - @Override - public int getHeaderKey() { - return HEADER; - } -} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupUserInvited.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupUserInvited.java deleted file mode 100644 index 8c6d31c876..0000000000 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupUserInvited.java +++ /dev/null @@ -1,97 +0,0 @@ -package im.actor.core.api.updates; -/* - * Generated by the Actor API Scheme generator. DO NOT EDIT! - */ - -import im.actor.runtime.bser.*; -import im.actor.runtime.collections.*; -import static im.actor.runtime.bser.Utils.*; -import im.actor.core.network.parser.*; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.NotNull; -import com.google.j2objc.annotations.ObjectiveCName; -import java.io.IOException; -import java.util.List; -import java.util.ArrayList; -import im.actor.core.api.*; - -public class UpdateGroupUserInvited extends Update { - - public static final int HEADER = 0x15; - public static UpdateGroupUserInvited fromBytes(byte[] data) throws IOException { - return Bser.parse(new UpdateGroupUserInvited(), data); - } - - private int groupId; - private long rid; - private int uid; - private int inviterUid; - private long date; - - public UpdateGroupUserInvited(int groupId, long rid, int uid, int inviterUid, long date) { - this.groupId = groupId; - this.rid = rid; - this.uid = uid; - this.inviterUid = inviterUid; - this.date = date; - } - - public UpdateGroupUserInvited() { - - } - - public int getGroupId() { - return this.groupId; - } - - public long getRid() { - return this.rid; - } - - public int getUid() { - return this.uid; - } - - public int getInviterUid() { - return this.inviterUid; - } - - public long getDate() { - return this.date; - } - - @Override - public void parse(BserValues values) throws IOException { - this.groupId = values.getInt(1); - this.rid = values.getLong(5); - this.uid = values.getInt(2); - this.inviterUid = values.getInt(3); - this.date = values.getLong(4); - } - - @Override - public void serialize(BserWriter writer) throws IOException { - writer.writeInt(1, this.groupId); - writer.writeLong(5, this.rid); - writer.writeInt(2, this.uid); - writer.writeInt(3, this.inviterUid); - writer.writeLong(4, this.date); - } - - @Override - public String toString() { - String res = "update GroupUserInvited{"; - res += "groupId=" + this.groupId; - res += ", rid=" + this.rid; - res += ", uid=" + this.uid; - res += ", inviterUid=" + this.inviterUid; - res += ", date=" + this.date; - res += "}"; - return res; - } - - @Override - public int getHeaderKey() { - return HEADER; - } -} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupUserKick.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupUserKick.java deleted file mode 100644 index 8df80a04e2..0000000000 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupUserKick.java +++ /dev/null @@ -1,97 +0,0 @@ -package im.actor.core.api.updates; -/* - * Generated by the Actor API Scheme generator. DO NOT EDIT! - */ - -import im.actor.runtime.bser.*; -import im.actor.runtime.collections.*; -import static im.actor.runtime.bser.Utils.*; -import im.actor.core.network.parser.*; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.NotNull; -import com.google.j2objc.annotations.ObjectiveCName; -import java.io.IOException; -import java.util.List; -import java.util.ArrayList; -import im.actor.core.api.*; - -public class UpdateGroupUserKick extends Update { - - public static final int HEADER = 0x18; - public static UpdateGroupUserKick fromBytes(byte[] data) throws IOException { - return Bser.parse(new UpdateGroupUserKick(), data); - } - - private int groupId; - private long rid; - private int uid; - private int kickerUid; - private long date; - - public UpdateGroupUserKick(int groupId, long rid, int uid, int kickerUid, long date) { - this.groupId = groupId; - this.rid = rid; - this.uid = uid; - this.kickerUid = kickerUid; - this.date = date; - } - - public UpdateGroupUserKick() { - - } - - public int getGroupId() { - return this.groupId; - } - - public long getRid() { - return this.rid; - } - - public int getUid() { - return this.uid; - } - - public int getKickerUid() { - return this.kickerUid; - } - - public long getDate() { - return this.date; - } - - @Override - public void parse(BserValues values) throws IOException { - this.groupId = values.getInt(1); - this.rid = values.getLong(5); - this.uid = values.getInt(2); - this.kickerUid = values.getInt(3); - this.date = values.getLong(4); - } - - @Override - public void serialize(BserWriter writer) throws IOException { - writer.writeInt(1, this.groupId); - writer.writeLong(5, this.rid); - writer.writeInt(2, this.uid); - writer.writeInt(3, this.kickerUid); - writer.writeLong(4, this.date); - } - - @Override - public String toString() { - String res = "update GroupUserKick{"; - res += "groupId=" + this.groupId; - res += ", rid=" + this.rid; - res += ", uid=" + this.uid; - res += ", kickerUid=" + this.kickerUid; - res += ", date=" + this.date; - res += "}"; - return res; - } - - @Override - public int getHeaderKey() { - return HEADER; - } -} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupUserLeave.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupUserLeave.java deleted file mode 100644 index dea7e60132..0000000000 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupUserLeave.java +++ /dev/null @@ -1,88 +0,0 @@ -package im.actor.core.api.updates; -/* - * Generated by the Actor API Scheme generator. DO NOT EDIT! - */ - -import im.actor.runtime.bser.*; -import im.actor.runtime.collections.*; -import static im.actor.runtime.bser.Utils.*; -import im.actor.core.network.parser.*; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.NotNull; -import com.google.j2objc.annotations.ObjectiveCName; -import java.io.IOException; -import java.util.List; -import java.util.ArrayList; -import im.actor.core.api.*; - -public class UpdateGroupUserLeave extends Update { - - public static final int HEADER = 0x17; - public static UpdateGroupUserLeave fromBytes(byte[] data) throws IOException { - return Bser.parse(new UpdateGroupUserLeave(), data); - } - - private int groupId; - private long rid; - private int uid; - private long date; - - public UpdateGroupUserLeave(int groupId, long rid, int uid, long date) { - this.groupId = groupId; - this.rid = rid; - this.uid = uid; - this.date = date; - } - - public UpdateGroupUserLeave() { - - } - - public int getGroupId() { - return this.groupId; - } - - public long getRid() { - return this.rid; - } - - public int getUid() { - return this.uid; - } - - public long getDate() { - return this.date; - } - - @Override - public void parse(BserValues values) throws IOException { - this.groupId = values.getInt(1); - this.rid = values.getLong(4); - this.uid = values.getInt(2); - this.date = values.getLong(3); - } - - @Override - public void serialize(BserWriter writer) throws IOException { - writer.writeInt(1, this.groupId); - writer.writeLong(4, this.rid); - writer.writeInt(2, this.uid); - writer.writeLong(3, this.date); - } - - @Override - public String toString() { - String res = "update GroupUserLeave{"; - res += "groupId=" + this.groupId; - res += ", rid=" + this.rid; - res += ", uid=" + this.uid; - res += ", date=" + this.date; - res += "}"; - return res; - } - - @Override - public int getHeaderKey() { - return HEADER; - } -} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsModule.java index 2c817b811c..f986db9634 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsModule.java @@ -8,16 +8,9 @@ import java.util.HashMap; import java.util.List; -import im.actor.core.ApiConfiguration; -import im.actor.core.api.ApiConfig; import im.actor.core.api.ApiGroupOutPeer; -import im.actor.core.api.ApiMessageAttributes; import im.actor.core.api.ApiOutPeer; -import im.actor.core.api.ApiPeer; import im.actor.core.api.ApiPeerType; -import im.actor.core.api.ApiServiceExGroupCreated; -import im.actor.core.api.ApiServiceExUserJoined; -import im.actor.core.api.ApiServiceMessage; import im.actor.core.api.ApiUserOutPeer; import im.actor.core.api.rpc.RequestCreateGroup; import im.actor.core.api.rpc.RequestEditGroupAbout; @@ -29,35 +22,17 @@ import im.actor.core.api.rpc.RequestJoinGroup; import im.actor.core.api.rpc.RequestKickUser; import im.actor.core.api.rpc.RequestLeaveGroup; -import im.actor.core.api.rpc.RequestMakeUserAdmin; import im.actor.core.api.rpc.RequestMakeUserAdminObsolete; import im.actor.core.api.rpc.RequestRevokeIntegrationToken; import im.actor.core.api.rpc.RequestRevokeInviteUrl; import im.actor.core.api.rpc.ResponseIntegrationToken; import im.actor.core.api.rpc.ResponseInviteUrl; -import im.actor.core.api.updates.UpdateContactsAdded; -import im.actor.core.api.updates.UpdateGroupAboutChanged; -import im.actor.core.api.updates.UpdateGroupAboutChangedObsolete; -import im.actor.core.api.updates.UpdateGroupMembersUpdate; -import im.actor.core.api.updates.UpdateGroupMembersUpdateObsolete; -import im.actor.core.api.updates.UpdateGroupTitleChanged; -import im.actor.core.api.updates.UpdateGroupTitleChangedObsolete; -import im.actor.core.api.updates.UpdateGroupTopicChanged; -import im.actor.core.api.updates.UpdateGroupTopicChangedObsolete; -import im.actor.core.api.updates.UpdateGroupUserInvited; -import im.actor.core.api.updates.UpdateGroupUserInvitedObsolete; -import im.actor.core.api.updates.UpdateGroupUserKick; -import im.actor.core.api.updates.UpdateGroupUserKickObsolete; -import im.actor.core.api.updates.UpdateGroupUserLeave; -import im.actor.core.api.updates.UpdateGroupUserLeaveObsolete; -import im.actor.core.api.updates.UpdateMessage; import im.actor.core.entity.Group; import im.actor.core.entity.Peer; import im.actor.core.entity.PeerType; import im.actor.core.entity.User; import im.actor.core.events.PeerChatOpened; import im.actor.core.events.PeerInfoOpened; -import im.actor.core.events.UserVisible; import im.actor.core.modules.AbsModule; import im.actor.core.modules.ModuleContext; import im.actor.core.modules.api.ApiSupportConfiguration; @@ -150,11 +125,7 @@ public Promise createGroup(final String title, final String avatarDescr api(new RequestCreateGroup(rid, title, apiUserOutPeers, null, ApiSupportConfiguration.OPTIMIZATIONS))) .chain(r -> updates().applyRelatedData(r.getUsers(), r.getGroup())) - .chain(r -> updates().applyUpdate(r.getSeq(), r.getState(), - new UpdateMessage( - new ApiPeer(ApiPeerType.GROUP, r.getGroup().getId()), myUid(), 0, rid, - new ApiServiceMessage("Group created", new ApiServiceExGroupCreated()), - null, null))) + .chain(r -> updates().waitForUpdate(r.getSeq())) .map(r -> r.getGroup().getId()) .then(integer -> { if (avatarDescriptor != null) { @@ -172,15 +143,7 @@ public Promise addMember(final int gid, final int uid) { rid, new ApiUserOutPeer(uid, groupUserTuple2.getT2().getAccessHash()), ApiSupportConfiguration.OPTIMIZATIONS))) - .flatMap(responseSeqDate -> - updates().applyUpdate( - responseSeqDate.getSeq(), - responseSeqDate.getState(), - new UpdateGroupUserInvitedObsolete( - gid, rid, - uid, myUid(), - responseSeqDate.getDate()) - )); + .flatMap(r -> updates().waitForUpdate(r.getSeq())); } public Promise kickMember(final int gid, final int uid) { @@ -192,17 +155,7 @@ public Promise kickMember(final int gid, final int uid) { rid, new ApiUserOutPeer(uid, groupUserTuple2.getT2().getAccessHash()), ApiSupportConfiguration.OPTIMIZATIONS))) - .flatMap(responseSeqDate -> - updates().applyUpdate( - responseSeqDate.getSeq(), - responseSeqDate.getState(), - new UpdateGroupUserKickObsolete( - gid, - rid, - uid, - myUid(), - responseSeqDate.getDate()) - )); + .flatMap(r -> updates().waitForUpdate(r.getSeq())); } public Promise leaveGroup(final int gid) { @@ -213,33 +166,17 @@ public Promise leaveGroup(final int gid) { new ApiGroupOutPeer(group.getGroupId(), group.getAccessHash()), rid, ApiSupportConfiguration.OPTIMIZATIONS))) - .flatMap(responseSeqDate -> - updates().applyUpdate( - responseSeqDate.getSeq(), - responseSeqDate.getState(), - new UpdateGroupUserLeaveObsolete( - gid, - rid, - myUid(), - responseSeqDate.getDate()) - )); + .flatMap(r -> updates().waitForUpdate(r.getSeq())); } - public Promise makeAdmin(final int gid, final int uid) { return Promises.tuple(getGroups().getValueAsync(gid), users().getValueAsync(uid)) .flatMap(groupUserTuple2 -> api(new RequestMakeUserAdminObsolete( new ApiGroupOutPeer(gid, groupUserTuple2.getT1().getAccessHash()), new ApiUserOutPeer(uid, groupUserTuple2.getT2().getAccessHash())))) - .flatMap(responseMakeUserAdmin -> - updates().applyUpdate( - responseMakeUserAdmin.getSeq(), - responseMakeUserAdmin.getState(), - new UpdateGroupMembersUpdateObsolete(gid, responseMakeUserAdmin.getMembers()) - )); + .flatMap(r -> updates().waitForUpdate(r.getSeq())); } - public Promise editTitle(final int gid, final String name) { final long rid = RandomUtils.nextRid(); return getGroups().getValueAsync(gid) @@ -248,10 +185,7 @@ public Promise editTitle(final int gid, final String name) { new ApiGroupOutPeer(group.getGroupId(), group.getAccessHash()), rid, name, ApiSupportConfiguration.OPTIMIZATIONS))) - .flatMap(responseSeqDate -> updates().applyUpdate( - responseSeqDate.getSeq(), - responseSeqDate.getState(), - new UpdateGroupTitleChanged(gid, name))); + .flatMap(r -> updates().waitForUpdate(r.getSeq())); } public Promise editTheme(final int gid, final String theme) { @@ -262,14 +196,7 @@ public Promise editTheme(final int gid, final String theme) { new ApiGroupOutPeer(group.getGroupId(), group.getAccessHash()), rid, theme, ApiSupportConfiguration.OPTIMIZATIONS))) - .flatMap(responseSeqDate -> updates().applyUpdate( - responseSeqDate.getSeq(), - responseSeqDate.getState(), - new UpdateGroupTopicChangedObsolete( - gid, rid, - myUid(), theme, - responseSeqDate.getDate()) - )); + .flatMap(r -> updates().waitForUpdate(r.getSeq())); } public Promise editAbout(final int gid, final String about) { @@ -279,12 +206,7 @@ public Promise editAbout(final int gid, final String about) { api(new RequestEditGroupAbout( new ApiGroupOutPeer(group.getGroupId(), group.getAccessHash()), rid, about, ApiSupportConfiguration.OPTIMIZATIONS))) - .flatMap(responseSeqDate -> updates().applyUpdate( - responseSeqDate.getSeq(), - responseSeqDate.getState(), - new UpdateGroupAboutChangedObsolete( - gid, about) - )); + .flatMap(r -> updates().waitForUpdate(r.getSeq())); } public void changeAvatar(int gid, String descriptor) { @@ -319,23 +241,7 @@ public Promise requestRevokeLink(final int gid) { public Promise joinGroupByToken(final String token) { return api(new RequestJoinGroup(token, ApiSupportConfiguration.OPTIMIZATIONS)) .chain(responseJoinGroup -> updates().loadRequiredPeers(responseJoinGroup.getUserPeers(), new ArrayList<>())) - .chain(responseJoinGroup -> - updates().applyUpdate( - responseJoinGroup.getSeq(), - responseJoinGroup.getState(), - new UpdateMessage( - new ApiPeer(ApiPeerType.GROUP, responseJoinGroup.getGroup().getId()), - myUid(), - responseJoinGroup.getDate(), - responseJoinGroup.getRid(), - new ApiServiceMessage("Joined chat", - new ApiServiceExUserJoined()), - new ApiMessageAttributes(), - null), - responseJoinGroup.getUsers(), - responseJoinGroup.getGroup() - ) - ) + .chain(r -> updates().waitForUpdate(r.getSeq())) .map(responseJoinGroup -> responseJoinGroup.getGroup().getId()); } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsProcessor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsProcessor.java index add3b8b6f7..559cb12adf 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsProcessor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsProcessor.java @@ -5,35 +5,22 @@ package im.actor.core.modules.groups; import im.actor.core.api.updates.UpdateGroupAboutChanged; -import im.actor.core.api.updates.UpdateGroupAboutChangedObsolete; import im.actor.core.api.updates.UpdateGroupAvatarChanged; -import im.actor.core.api.updates.UpdateGroupAvatarChangedObsolete; import im.actor.core.api.updates.UpdateGroupCanInviteMembersChanged; import im.actor.core.api.updates.UpdateGroupCanSendMessagesChanged; import im.actor.core.api.updates.UpdateGroupCanViewMembersChanged; import im.actor.core.api.updates.UpdateGroupExtChanged; import im.actor.core.api.updates.UpdateGroupFullExtChanged; import im.actor.core.api.updates.UpdateGroupHistoryShared; -import im.actor.core.api.updates.UpdateGroupInvite; -import im.actor.core.api.updates.UpdateGroupInviteObsolete; import im.actor.core.api.updates.UpdateGroupMemberAdminChanged; import im.actor.core.api.updates.UpdateGroupMemberChanged; import im.actor.core.api.updates.UpdateGroupMemberDiff; import im.actor.core.api.updates.UpdateGroupMembersBecameAsync; import im.actor.core.api.updates.UpdateGroupMembersCountChanged; -import im.actor.core.api.updates.UpdateGroupMembersUpdate; -import im.actor.core.api.updates.UpdateGroupMembersUpdateObsolete; +import im.actor.core.api.updates.UpdateGroupMembersUpdated; import im.actor.core.api.updates.UpdateGroupOwnerChanged; import im.actor.core.api.updates.UpdateGroupTitleChanged; -import im.actor.core.api.updates.UpdateGroupTitleChangedObsolete; import im.actor.core.api.updates.UpdateGroupTopicChanged; -import im.actor.core.api.updates.UpdateGroupTopicChangedObsolete; -import im.actor.core.api.updates.UpdateGroupUserInvited; -import im.actor.core.api.updates.UpdateGroupUserInvitedObsolete; -import im.actor.core.api.updates.UpdateGroupUserKick; -import im.actor.core.api.updates.UpdateGroupUserKickObsolete; -import im.actor.core.api.updates.UpdateGroupUserLeave; -import im.actor.core.api.updates.UpdateGroupUserLeaveObsolete; import im.actor.core.modules.AbsModule; import im.actor.core.modules.ModuleContext; import im.actor.core.modules.sequence.processor.SequenceProcessor; @@ -55,7 +42,7 @@ public Promise process(Update update) { update instanceof UpdateGroupAvatarChanged || update instanceof UpdateGroupExtChanged || - update instanceof UpdateGroupMembersUpdate || + update instanceof UpdateGroupMembersUpdated || update instanceof UpdateGroupMemberAdminChanged || update instanceof UpdateGroupMemberDiff || update instanceof UpdateGroupMembersBecameAsync || diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/router/GroupRouter.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/router/GroupRouter.java index 97750d1439..8708c67d43 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/router/GroupRouter.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/router/GroupRouter.java @@ -25,7 +25,7 @@ import im.actor.core.api.updates.UpdateGroupMemberDiff; import im.actor.core.api.updates.UpdateGroupMembersBecameAsync; import im.actor.core.api.updates.UpdateGroupMembersCountChanged; -import im.actor.core.api.updates.UpdateGroupMembersUpdate; +import im.actor.core.api.updates.UpdateGroupMembersUpdated; import im.actor.core.api.updates.UpdateGroupOwnerChanged; import im.actor.core.api.updates.UpdateGroupTitleChanged; import im.actor.core.api.updates.UpdateGroupTopicChanged; @@ -302,8 +302,8 @@ private Promise onUpdate(Update update) { // Members // - else if (update instanceof UpdateGroupMembersUpdate) { - UpdateGroupMembersUpdate membersUpdate = (UpdateGroupMembersUpdate) update; + else if (update instanceof UpdateGroupMembersUpdated) { + UpdateGroupMembersUpdated membersUpdate = (UpdateGroupMembersUpdated) update; return onMembersChanged(membersUpdate.getGroupId(), membersUpdate.getMembers()); } else if (update instanceof UpdateGroupMemberAdminChanged) { UpdateGroupMemberAdminChanged adminChanged = (UpdateGroupMemberAdminChanged) update; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/sequence/SequenceActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/sequence/SequenceActor.java index 7d76f08571..6fc595db2f 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/sequence/SequenceActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/sequence/SequenceActor.java @@ -43,6 +43,7 @@ public static ActorCreator CONSTRUCTOR(final ModuleContext context) { private static final String TAG = "Updates"; private static final int INVALIDATE_GAP = 2000;// 2 Secs private static final int INVALIDATE_MAX_SEC_HOLE = 10; + private static final boolean PROCESS_EXTERNAL_PUSH_SEQ = false; private static final String KEY_SEQ = "updates_seq"; private static final String KEY_STATE = "updates_state"; @@ -101,11 +102,13 @@ private void onWeakUpdateReceived(int type, byte[] body, long date) { } private void onPushSeqReceived(int seq) { - if (seq <= this.seq) { - Log.d(TAG, "Ignored PushSeq {seq:" + seq + "}"); - } else { - Log.w(TAG, "External Out of sequence: starting timer for invalidation"); - startInvalidationTimer(); + if (PROCESS_EXTERNAL_PUSH_SEQ) { + if (seq <= this.seq) { + Log.d(TAG, "Ignored PushSeq {seq:" + seq + "}"); + } else { + Log.w(TAG, "External Out of sequence: starting timer for invalidation"); + startInvalidationTimer(); + } } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/sequence/Updates.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/sequence/Updates.java index 9f0a465fca..22d0c8843d 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/sequence/Updates.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/sequence/Updates.java @@ -4,6 +4,8 @@ package im.actor.core.modules.sequence; +import org.jetbrains.annotations.NotNull; + import java.util.ArrayList; import java.util.List; @@ -25,6 +27,7 @@ import im.actor.runtime.eventbus.Event; import im.actor.runtime.promise.Promise; import im.actor.runtime.promise.PromiseFunc; +import im.actor.runtime.promise.PromiseResolver; import im.actor.runtime.promise.Promises; import im.actor.runtime.promise.PromisesArray; @@ -62,7 +65,6 @@ public void onPushReceived(int seq) { } } - public Promise applyUpdate(int seq, byte[] state, Update update) { return new Promise<>((PromiseFunc) resolver -> { updateActor.send(new SeqUpdate(seq, state, update.getHeaderKey(), update.toByteArray())); @@ -75,7 +77,6 @@ public Promise applyUpdate(int seq, byte[] state, Update update, ArrayList groups = new ArrayList<>(); groups.add(group); return applyUpdate(seq, state, update, users, groups); - } public Promise applyUpdate(int seq, byte[] state, Update update, @@ -87,6 +88,11 @@ public Promise applyUpdate(int seq, byte[] state, Update update, }); } + public Promise waitForUpdate(int seq) { + return new Promise<>((PromiseFunc) resolver -> { + executeAfter(seq, () -> resolver.result(null)); + }); + } public Promise applyRelatedData(final List users) { return applyRelatedData(users, new ArrayList<>()); diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/sequence/processor/UpdateValidator.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/sequence/processor/UpdateValidator.java index 430d13d96e..21f55884fd 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/sequence/processor/UpdateValidator.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/sequence/processor/UpdateValidator.java @@ -13,16 +13,12 @@ import im.actor.core.api.updates.UpdateContactsRemoved; import im.actor.core.api.updates.UpdateGroupExtChanged; import im.actor.core.api.updates.UpdateGroupFullExtChanged; -import im.actor.core.api.updates.UpdateGroupInvite; import im.actor.core.api.updates.UpdateGroupMemberAdminChanged; import im.actor.core.api.updates.UpdateGroupMemberChanged; import im.actor.core.api.updates.UpdateGroupMemberDiff; import im.actor.core.api.updates.UpdateGroupMembersCountChanged; -import im.actor.core.api.updates.UpdateGroupMembersUpdate; +import im.actor.core.api.updates.UpdateGroupMembersUpdated; import im.actor.core.api.updates.UpdateGroupOwnerChanged; -import im.actor.core.api.updates.UpdateGroupUserInvited; -import im.actor.core.api.updates.UpdateGroupUserKick; -import im.actor.core.api.updates.UpdateGroupUserLeave; import im.actor.core.api.updates.UpdateMessage; import im.actor.core.api.updates.UpdateUserLocalNameChanged; import im.actor.core.modules.AbsModule; @@ -53,24 +49,6 @@ public boolean isCausesInvalidation(Update update) { } else if (update instanceof UpdateContactRegistered) { UpdateContactRegistered contactRegistered = (UpdateContactRegistered) update; users.add(contactRegistered.getUid()); - } else if (update instanceof UpdateGroupInvite) { - UpdateGroupInvite groupInvite = (UpdateGroupInvite) update; - users.add(groupInvite.getInviteUid()); - groups.add(groupInvite.getGroupId()); - } else if (update instanceof UpdateGroupUserInvited) { - UpdateGroupUserInvited invited = (UpdateGroupUserInvited) update; - users.add(invited.getInviterUid()); - users.add(invited.getUid()); - groups.add(invited.getGroupId()); - } else if (update instanceof UpdateGroupUserKick) { - UpdateGroupUserKick kick = (UpdateGroupUserKick) update; - users.add(kick.getKickerUid()); - users.add(kick.getUid()); - groups.add(kick.getGroupId()); - } else if (update instanceof UpdateGroupUserLeave) { - UpdateGroupUserLeave leave = (UpdateGroupUserLeave) update; - users.add(leave.getUid()); - groups.add(leave.getGroupId()); } else if (update instanceof UpdateContactsAdded) { users.addAll(((UpdateContactsAdded) update).getUids()); } else if (update instanceof UpdateContactsRemoved) { @@ -102,8 +80,8 @@ public boolean isCausesInvalidation(Update update) { users.add(m.getInviterUid()); users.add(m.getUid()); } - } else if (update instanceof UpdateGroupMembersUpdate) { - UpdateGroupMembersUpdate u = (UpdateGroupMembersUpdate) update; + } else if (update instanceof UpdateGroupMembersUpdated) { + UpdateGroupMembersUpdated u = (UpdateGroupMembersUpdated) update; groups.add(u.getGroupId()); for (ApiMember m : u.getMembers()) { users.add(m.getInviterUid()); From cd091a1ccff0d51c9456409527c3e8860ee8fa81 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Wed, 13 Jul 2016 06:18:37 +0300 Subject: [PATCH 008/414] fix(android): Fixing missing search on invite to group --- .../sdk/controllers/group/AddMemberActivity.java | 8 +------- .../sdk/controllers/group/AddMemberFragment.java | 11 +++++++---- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/AddMemberActivity.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/AddMemberActivity.java index cad1ea9d69..e036571294 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/AddMemberActivity.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/AddMemberActivity.java @@ -7,18 +7,12 @@ public class AddMemberActivity extends BaseFragmentActivity { - private int gid; - @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - getSupportActionBar().setTitle(R.string.group_add_title); - - gid = getIntent().getIntExtra("GROUP_ID", 0); - if (savedInstanceState == null) { - showFragment(AddMemberFragment.create(gid), false, false); + showFragment(AddMemberFragment.create(getIntent().getIntExtra("GROUP_ID", 0)), false, false); } } } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/AddMemberFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/AddMemberFragment.java index 850264d732..232b138d9c 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/AddMemberFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/AddMemberFragment.java @@ -22,10 +22,6 @@ public class AddMemberFragment extends BaseContactFragment { - public AddMemberFragment() { - super(true, true, false); - } - public static AddMemberFragment create(int gid) { AddMemberFragment res = new AddMemberFragment(); Bundle arguments = new Bundle(); @@ -34,6 +30,13 @@ public static AddMemberFragment create(int gid) { return res; } + public AddMemberFragment() { + super(true, true, false); + setRootFragment(true); + setTitle(R.string.group_add_title); + setHomeAsUp(true); + } + @Override protected void addFootersAndHeaders() { addFooterOrHeaderAction(ActorSDK.sharedActor().style.getActionAddContactColor(), R.drawable.ic_person_add_white_24dp, R.string.contacts_invite_via_link, false, new Runnable() { From f10212d33abc7d33903da9524cb7381198fd1daf Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Wed, 13 Jul 2016 06:22:57 +0300 Subject: [PATCH 009/414] fix(core): Fixing handling RPC responses on deserialization errors --- .../src/main/java/im/actor/core/network/api/ApiBroker.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/network/api/ApiBroker.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/network/api/ApiBroker.java index ed88c1d40e..ce3fe2c249 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/network/api/ApiBroker.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/network/api/ApiBroker.java @@ -256,6 +256,11 @@ private void processResponse(long authId, long mid, byte[] content) { response = (Response) parserConfig.parseRpc(ok.responseType, ok.payload); } catch (IOException e) { e.printStackTrace(); + requests.remove(rid); + if (holder.protoId != 0) { + idMap.remove(holder.protoId); + } + holder.callback.onError(new RpcInternalException()); return; } From c0e96fe5fe2ba8e3485ef045e4985ba89a61a9ef Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Wed, 13 Jul 2016 08:34:28 +0300 Subject: [PATCH 010/414] ref,feat(android+core): Refactoring of groups fragment, group owner transfership --- .../im/actor/sdk/controllers/ActorBinder.java | 7 + .../sdk/controllers/BinderCompatFragment.java | 5 + .../controllers/group/GroupInfoActivity.java | 3 +- .../controllers/group/GroupInfoFragment.java | 514 ++++++++---------- .../main/res/layout/fragment_group_header.xml | 4 +- .../src/main/res/values-ru/ui_text.xml | 4 +- .../src/main/res/values/ui_text.xml | 4 +- .../main/java/im/actor/core/Messenger.java | 13 + .../core/modules/groups/GroupsModule.java | 9 + .../java/im/actor/core/viewmodel/GroupVM.java | 19 +- .../java/im/actor/core/viewmodel/UserVM.java | 15 +- .../java/im/actor/runtime/mvvm/Value.java | 5 +- .../im/actor/runtime/mvvm/ValueListener.java | 12 + 13 files changed, 292 insertions(+), 322 deletions(-) create mode 100644 actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/mvvm/ValueListener.java diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/ActorBinder.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/ActorBinder.java index 6ab9a568ea..72f1b7f15e 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/ActorBinder.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/ActorBinder.java @@ -14,6 +14,7 @@ import im.actor.core.viewmodel.GroupVM; import im.actor.core.viewmodel.UserPresence; import im.actor.core.viewmodel.UserVM; +import im.actor.runtime.mvvm.ValueListener; import im.actor.runtime.mvvm.ValueModel; import im.actor.sdk.ActorSDK; import im.actor.sdk.R; @@ -229,6 +230,12 @@ public Binding bind(Value value, ValueChangedListener listener) { return b; } + public Binding bind(Value value, ValueListener listener) { + return bind(value, (val, valueModel) -> { + listener.onChanged(val); + }); + } + public Binding bind(Value value, ValueChangedListener listener, boolean notify) { value.subscribe(listener, notify); Binding b = new Binding(value, listener); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/BinderCompatFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/BinderCompatFragment.java index 9783cc10a6..6e247a6940 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/BinderCompatFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/BinderCompatFragment.java @@ -8,6 +8,7 @@ import im.actor.core.viewmodel.UserVM; import im.actor.runtime.mvvm.Value; import im.actor.runtime.mvvm.ValueDoubleChangedListener; +import im.actor.runtime.mvvm.ValueListener; import im.actor.sdk.view.avatar.AvatarView; import im.actor.sdk.view.avatar.CoverAvatarView; import im.actor.runtime.mvvm.ValueChangedListener; @@ -35,6 +36,10 @@ public void bind(ValueModel value, ValueChangedListener listener) { BINDER.bind(value, listener); } + public void bind(ValueModel value, ValueListener listener) { + BINDER.bind(value, listener); + } + public void bind(ValueModel value, boolean notify, ValueChangedListener listener) { BINDER.bind(value, listener, notify); } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoActivity.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoActivity.java index 8edc540316..5764a2a927 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoActivity.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoActivity.java @@ -16,8 +16,7 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); int chatId = getIntent().getIntExtra(Intents.EXTRA_GROUP_ID, 0); - getSupportActionBar().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); - getSupportActionBar().setTitle(null); + if (savedInstanceState == null) { GroupInfoFragment fragment; diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java index e97638cbf7..f014ede688 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java @@ -1,12 +1,11 @@ package im.actor.sdk.controllers.group; -import android.app.Activity; import android.app.AlertDialog; -import android.content.DialogInterface; import android.content.Intent; import android.graphics.Color; import android.graphics.drawable.ColorDrawable; import android.os.Bundle; +import android.support.v7.app.ActionBar; import android.support.v7.widget.SwitchCompat; import android.view.LayoutInflater; import android.view.Menu; @@ -25,7 +24,6 @@ import com.google.i18n.phonenumbers.Phonenumber; import java.util.ArrayList; -import java.util.HashSet; import im.actor.core.entity.GroupMember; import im.actor.core.entity.Peer; @@ -40,7 +38,6 @@ import im.actor.sdk.R; import im.actor.sdk.controllers.Intents; import im.actor.sdk.controllers.activity.BaseActivity; -import im.actor.sdk.controllers.ActorBinder; import im.actor.sdk.controllers.BaseFragment; import im.actor.sdk.controllers.group.view.MembersAdapter; import im.actor.sdk.controllers.fragment.preview.ViewAvatarActivity; @@ -49,8 +46,6 @@ import im.actor.sdk.view.adapters.RecyclerListView; import im.actor.sdk.view.avatar.CoverAvatarView; import im.actor.sdk.util.Fonts; -import im.actor.runtime.mvvm.ValueChangedListener; -import im.actor.runtime.mvvm.Value; import static im.actor.sdk.util.ActorSDKMessenger.groups; import static im.actor.sdk.util.ActorSDKMessenger.messenger; @@ -69,21 +64,24 @@ public static GroupInfoFragment create(int chatId) { return res; } - private String[] theme; - private String[] about; private int chatId; - private GroupVM groupInfo; + private GroupVM groupVM; + private RecyclerListView listView; - private MembersAdapter groupUserAdapter; private CoverAvatarView avatarView; + private MembersAdapter groupUserAdapter; private View notMemberView; - protected View header; - private boolean isAdmin; @Override public void onCreate(Bundle saveInstance) { super.onCreate(saveInstance); - setHasOptionsMenu(true); + setRootFragment(true); + } + + @Override + public void onConfigureActionBar(ActionBar actionBar) { + super.onConfigureActionBar(actionBar); + actionBar.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); } @Override @@ -91,240 +89,178 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa chatId = getArguments().getInt(EXTRA_CHAT_ID); - groupInfo = groups().get(chatId); + groupVM = groups().get(chatId); View res = inflater.inflate(R.layout.fragment_group, container, false); - res.setBackgroundColor(ActorSDK.sharedActor().style.getMainBackgroundColor()); + listView = (RecyclerListView) res.findViewById(R.id.groupList); notMemberView = res.findViewById(R.id.notMember); - notMemberView.setBackgroundColor(ActorSDK.sharedActor().style.getMainBackgroundColor()); - ActorStyle style = ActorSDK.sharedActor().style; - ((TextView) notMemberView.findViewById(R.id.not_member_text)).setTextColor(style.getTextPrimaryColor()); - bind(groupInfo.isMember(), new ValueChangedListener() { - @Override - public void onChanged(Boolean val, Value Value) { - notMemberView.setVisibility(val ? View.GONE : View.VISIBLE); - getActivity().invalidateOptionsMenu(); - } - }); - listView = (RecyclerListView) res.findViewById(R.id.groupList); + res.setBackgroundColor(style.getBackyardBackgroundColor()); listView.setBackgroundColor(style.getMainBackgroundColor()); + notMemberView.setBackgroundColor(style.getMainBackgroundColor()); + ((TextView) notMemberView.findViewById(R.id.not_member_text)).setTextColor(style.getTextPrimaryColor()); - header = inflater.inflate(R.layout.fragment_group_header, listView, false); + // + // Header + // + + // Views + View header = inflater.inflate(R.layout.fragment_group_header, listView, false); + avatarView = (CoverAvatarView) header.findViewById(R.id.avatar); + TextView aboutTV = (TextView) header.findViewById(R.id.about); + View aboutCont = header.findViewById(R.id.aboutContainer); + TextView title = (TextView) header.findViewById(R.id.title); + TextView createdBy = (TextView) header.findViewById(R.id.createdBy); + View descriptionContainer = header.findViewById(R.id.descriptionContainer); + SwitchCompat isNotificationsEnabled = (SwitchCompat) header.findViewById(R.id.enableNotifications); + TextView memberCount = (TextView) header.findViewById(R.id.membersCount); + TextView settingsHeaderText = (TextView) header.findViewById(R.id.settings_header_text); + TintImageView notificationSettingIcon = (TintImageView) header.findViewById(R.id.settings_notification_icon); + TextView membersHeaderText = (TextView) header.findViewById(R.id.membersTitle); + + // Styling + ((TextView) header.findViewById(R.id.about_hint)).setTextColor(style.getTextSecondaryColor()); header.setBackgroundColor(style.getMainBackgroundColor()); + title.setTextColor(style.getProfileTitleColor()); + createdBy.setTextColor(style.getProfileSubtitleColor()); + aboutTV.setTextColor(style.getTextPrimaryColor()); + // themeHeader.setTextColor(style.getProfileSubtitleColor()); + memberCount.setTextColor(style.getTextHintColor()); + settingsHeaderText.setTextColor(style.getSettingsCategoryTextColor()); + notificationSettingIcon.setTint(style.getSettingsIconColor()); + membersHeaderText.setTextColor(style.getSettingsCategoryTextColor()); + ((TextView) header.findViewById(R.id.settings_notifications_title)).setTextColor(style.getTextPrimaryColor()); + header.findViewById(R.id.after_about_divider).setBackgroundColor(style.getBackyardBackgroundColor()); + header.findViewById(R.id.after_settings_divider).setBackgroundColor(style.getBackyardBackgroundColor()); + // Avatar - avatarView = (CoverAvatarView) header.findViewById(R.id.avatar); - bind(avatarView, groupInfo.getAvatar()); - avatarView.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - startActivity(ViewAvatarActivity.viewGroupAvatar(chatId, getActivity())); - } + bind(avatarView, groupVM.getAvatar()); + avatarView.setOnClickListener(view -> { + startActivity(ViewAvatarActivity.viewGroupAvatar(chatId, getActivity())); }); // Title - TextView title = (TextView) header.findViewById(R.id.title); - title.setTextColor(style.getProfileTitleColor()); - bind(title, groupInfo.getName()); + bind(title, groupVM.getName()); - // Created by - isAdmin = false; - final TextView createdBy = (TextView) header.findViewById(R.id.createdBy); - createdBy.setTextColor(style.getProfileSubtitleColor()); - if (groupInfo.getCreatorId() == myUid()) { - createdBy.setText(R.string.group_created_by_you); - isAdmin = true; - } else { -// UserVM admin = users().get(groupInfo.getCreatorId()); -// bind(admin.getName(), new ValueChangedListener() { -// @Override -// public void onChanged(String val, Value Value) { -// createdBy.setText(getString(R.string.group_created_by).replace("{0}", val)); -// } -// }); - } + // Owned by + bind(groupVM.getOwnerId(), ownerId -> { + if (ownerId != 0) { + if (ownerId == myUid()) { + createdBy.setText(R.string.group_created_by_you); + } else { + String ownerName = users().get(ownerId).getName().get(); + createdBy.setText(getString(R.string.group_created_by).replace("{0}", ownerName)); + } + } else { + createdBy.setText(""); + } + }); - //Description - theme = new String[1]; - about = new String[1]; - // TextView themeTV = (TextView) header.findViewById(R.id.theme); - // themeTV.setTextColor(style.getTextPrimaryColor()); - // ((TextView) header.findViewById(R.id.group_theme_hint)).setTextColor(style.getTextSecondaryColor()); - TextView aboutTV = (TextView) header.findViewById(R.id.about); - aboutTV.setTextColor(style.getTextPrimaryColor()); - ((TextView) header.findViewById(R.id.about_hint)).setTextColor(style.getTextSecondaryColor()); - final View descriptionContainer = header.findViewById(R.id.descriptionContainer); - final TextView themeHeader = (TextView) header.findViewById(R.id.theme_header); - themeHeader.setTextColor(style.getProfileSubtitleColor()); - - final boolean finalIsAdmin = isAdmin; -// bind(themeTV, header.findViewById(R.id.themeContainer), groupInfo.getTheme(), new ActorBinder.OnChangedListener() { -// @Override -// public void onChanged(String s) { -// theme[0] = s; -// updateDescriptionVisibility(descriptionContainer, finalIsAdmin, header); -// } -// }, !isAdmin, getString(R.string.theme_group_empty)); - - //bind(themeHeader, themeHeader, groupInfo.getTheme()); - - bind(aboutTV, header.findViewById(R.id.aboutContainer), groupInfo.getAbout(), new ActorBinder.OnChangedListener() { - @Override - public void onChanged(String s) { - about[0] = s; - updateDescriptionVisibility(descriptionContainer, finalIsAdmin, header); + // About + + bind(groupVM.getOwnerId(), groupVM.getAbout(), (ownerId, valueModel, theme, valueModel2) -> { + boolean isVisible; + if (theme != null) { + isVisible = true; + aboutTV.setText(theme); + } else { + aboutTV.setText(R.string.about_group_empty); + isVisible = ownerId == myUid(); } - }, !isAdmin, getString(R.string.about_group_empty)); - - if (isAdmin) { -// header.findViewById(R.id.themeContainer).setOnClickListener(new View.OnClickListener() { -// @Override -// public void onClick(View v) { -// startActivity(Intents.editGroupTheme(groupInfo.getId(), getActivity())); -// } -// }); - - header.findViewById(R.id.aboutContainer).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - startActivity(Intents.editGroupAbout(groupInfo.getId(), getActivity())); - } - }); - } - // Settings + descriptionContainer.setVisibility(isVisible ? View.VISIBLE : View.GONE); + + if (ownerId == myUid()) { + aboutCont.setOnClickListener(view -> { + startActivity(Intents.editGroupAbout(groupVM.getId(), getActivity())); + }); + } else { + aboutCont.setOnClickListener(null); + } + }); + + // Notifications - final SwitchCompat isNotificationsEnabled = (SwitchCompat) header.findViewById(R.id.enableNotifications); isNotificationsEnabled.setChecked(messenger().isNotificationsEnabled(Peer.group(chatId))); - isNotificationsEnabled.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - messenger().changeNotificationsEnabled(Peer.group(chatId), isChecked); - } + isNotificationsEnabled.setOnCheckedChangeListener((buttonView, isChecked) -> { + messenger().changeNotificationsEnabled(Peer.group(chatId), isChecked); }); - header.findViewById(R.id.notificationsCont).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - isNotificationsEnabled.setChecked(!isNotificationsEnabled.isChecked()); + header.findViewById(R.id.notificationsCont).setOnClickListener(v -> { + isNotificationsEnabled.setChecked(!isNotificationsEnabled.isChecked()); + }); + + // Members + + bind(groupVM.getMembersCount(), val -> { + if (val != null) { + memberCount.setText(val + ""); + } else { + memberCount.setText(""); } }); - //Members - TextView memberCount = (TextView) header.findViewById(R.id.membersCount); - memberCount.setText(groupInfo.getMembersCount().get() + ""); - memberCount.setTextColor(style.getTextHintColor()); listView.addHeaderView(header, null, false); - View add = inflater.inflate(R.layout.fragment_group_add, listView, false); - add.findViewById(R.id.bottom_divider).setBackgroundColor(style.getBackyardBackgroundColor()); - TextView name = (TextView) add.findViewById(R.id.name); + + // + // Footer + // + + // View + View footer = inflater.inflate(R.layout.fragment_group_add, listView, false); + TextView name = (TextView) footer.findViewById(R.id.name); + TintImageView addIcon = (TintImageView) footer.findViewById(R.id.add_icon); + + // Style + footer.findViewById(R.id.bottom_divider).setBackgroundColor(style.getBackyardBackgroundColor()); name.setTextColor(style.getActionAddContactColor()); name.setTypeface(Fonts.medium()); - TintImageView addIcon = (TintImageView) add.findViewById(R.id.add_icon); addIcon.setTint(style.getGroupActionAddIconColor()); addIcon.setTint(style.getActionAddContactColor()); - add.findViewById(R.id.addUser).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - startActivity(new Intent(getActivity(), AddMemberActivity.class) - .putExtra("GROUP_ID", chatId)); - } + footer.findViewById(R.id.addUser).setOnClickListener(v -> { + startActivity(new Intent(getActivity(), AddMemberActivity.class) + .putExtra("GROUP_ID", chatId)); }); - listView.addFooterView(add, null, false); - groupUserAdapter = new MembersAdapter(groupInfo.getMembers().get(), getActivity()); - bind(groupInfo.getMembers(), new ValueChangedListener>() { - @Override - public void onChanged(HashSet val, Value> Value) { - groupUserAdapter.updateUid(val); - } + listView.addFooterView(footer, null, false); + + + // + // Members + // + + groupUserAdapter = new MembersAdapter(groupVM.getMembers().get(), getActivity()); + bind(groupVM.getMembers(), members -> { + groupUserAdapter.updateUid(members); }); listView.setAdapter(groupUserAdapter); - listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { - @Override - public void onItemClick(AdapterView parent, View view, int position, long id) { - Object item = parent.getItemAtPosition(position); - if (item != null && item instanceof GroupMember) { - final GroupMember groupMember = (GroupMember) item; - if (groupMember.getUid() == myUid()) { - return; + listView.setOnItemClickListener((parent, view, position, id) -> { + Object item = parent.getItemAtPosition(position); + if (item != null && item instanceof GroupMember) { + GroupMember groupMember = (GroupMember) item; + if (groupMember.getUid() != myUid()) { + UserVM userVM = users().get(groupMember.getUid()); + if (userVM != null) { + startActivity(Intents.openPrivateDialog(userVM.getId(), true, getActivity())); } - final UserVM userVM = users().get(groupMember.getUid()); - if (userVM == null) { - return; + } + } + }); + listView.setOnItemLongClickListener((adapterView, view, i, l) -> { + Object item = adapterView.getItemAtPosition(i); + if (item != null && item instanceof GroupMember) { + GroupMember groupMember = (GroupMember) item; + if (groupMember.getUid() != myUid()) { + UserVM userVM = users().get(groupMember.getUid()); + if (userVM != null) { + onMemberClicked(userVM); + return true; } - new AlertDialog.Builder(getActivity()) - .setItems(new CharSequence[]{ - getString(R.string.group_context_message).replace("{0}", userVM.getName().get()), - getString(R.string.group_context_call).replace("{0}", userVM.getName().get()), - getString(R.string.group_context_view).replace("{0}", userVM.getName().get()), - getString(R.string.group_context_remove).replace("{0}", userVM.getName().get()), - }, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - if (which == 0) { - startActivity(Intents.openPrivateDialog(userVM.getId(), true, getActivity())); - } else if (which == 1) { - final ArrayList phones = userVM.getPhones().get(); - if (phones.size() == 0) { - Toast.makeText(getActivity(), "No phones available", Toast.LENGTH_SHORT).show(); - } else if (phones.size() == 1) { - startActivity(Intents.call(phones.get(0).getPhone())); - } else { - CharSequence[] sequences = new CharSequence[phones.size()]; - for (int i = 0; i < sequences.length; i++) { - try { - Phonenumber.PhoneNumber number = PhoneNumberUtil.getInstance().parse("+" + phones.get(i).getPhone(), "us"); - sequences[i] = phones.get(which).getTitle() + ": " + PhoneNumberUtil.getInstance().format(number, PhoneNumberUtil.PhoneNumberFormat.INTERNATIONAL); - } catch (NumberParseException e) { - e.printStackTrace(); - sequences[i] = phones.get(which).getTitle() + ": +" + phones.get(i).getPhone(); - } - } - new AlertDialog.Builder(getActivity()) - .setItems(sequences, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - startActivity(Intents.call(phones.get(which).getPhone())); - } - }) - .show() - .setCanceledOnTouchOutside(true); - } - } else if (which == 2) { - ActorSDKLauncher.startProfileActivity(getActivity(), userVM.getId()); - } else if (which == 3) { - new AlertDialog.Builder(getActivity()) - .setMessage(getString(R.string.alert_group_remove_text).replace("{0}", userVM.getName().get())) - .setPositiveButton(R.string.alert_group_remove_yes, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog2, int which) { - execute(messenger().kickMember(chatId, userVM.getId()), - R.string.progress_common, new CommandCallback() { - @Override - public void onResult(Void res) { - - } - - @Override - public void onError(Exception e) { - Toast.makeText(getActivity(), R.string.toast_unable_kick, Toast.LENGTH_SHORT).show(); - } - }); - } - }) - .setNegativeButton(R.string.dialog_cancel, null) - .show() - .setCanceledOnTouchOutside(true); - } - } - }) - .show() - .setCanceledOnTouchOutside(true); } } + return false; }); listView.setOnScrollListener(new AbsListView.OnScrollListener() { @@ -348,47 +284,82 @@ public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCoun } }); - res.findViewById(R.id.after_about_divider).setBackgroundColor(style.getBackyardBackgroundColor()); - res.findViewById(R.id.after_settings_divider).setBackgroundColor(style.getBackyardBackgroundColor()); - res.findViewById(R.id.bottom_divider).setBackgroundColor(style.getBackyardBackgroundColor()); - + // + // Placeholder + // - TextView settingsHeaderText = (TextView) res.findViewById(R.id.settings_header_text); - settingsHeaderText.setTextColor(style.getSettingsCategoryTextColor()); - TintImageView notificationSettingIcon = (TintImageView) res.findViewById(R.id.settings_notification_icon); - notificationSettingIcon.setTint(style.getSettingsIconColor()); - ((TextView) res.findViewById(R.id.settings_notifications_title)).setTextColor(style.getTextPrimaryColor()); - -// TintImageView shareMediaIcon = (TintImageView) res.findViewById(R.id.share_media_icon); -// shareMediaIcon.setTint(style.getSettingsIconColor()); -// ((TextView) res.findViewById(R.id.settings_media_title)).setTextColor(style.getTextPrimaryColor()); -// ((TextView) res.findViewById(R.id.mediaCount)).setTextColor(style.getTextHintColor()); -// -// TintImageView shareDocsIcon = (TintImageView) res.findViewById(R.id.share_docs_icon); -// shareDocsIcon.setTint(style.getSettingsIconColor()); -// ((TextView) res.findViewById(R.id.share_docs_title)).setTextColor(style.getTextPrimaryColor()); -// ((TextView) res.findViewById(R.id.docCount)).setTextColor(style.getTextHintColor()); - -// TextView sharedHeaderText = (TextView) res.findViewById(R.id.shared_header_text); -// sharedHeaderText.setTextColor(style.getSettingsCategoryTextColor()); - - TextView membersHeaderText = (TextView) res.findViewById(R.id.membersTitle); - membersHeaderText.setTextColor(style.getSettingsCategoryTextColor()); + bind(groupVM.isMember(), (isMember) -> { + notMemberView.setVisibility(isMember ? View.GONE : View.VISIBLE); + getActivity().invalidateOptionsMenu(); + }); return res; } - public void updateDescriptionVisibility(View descriptionContainer, boolean finalIsAdmin, View header) { - // View themeDivider = header.findViewById(R.id.themeDivider); - // themeDivider.setBackgroundColor(ActorSDK.sharedActor().style.getDividerColor()); - - boolean themeVis = theme[0] != null && !theme[0].isEmpty(); - boolean aboutVis = about[0] != null && !about[0].isEmpty(); + public void onMemberClicked(UserVM userVM) { + + boolean isOwned = groupVM.getOwnerId().get() == myUid(); + + new AlertDialog.Builder(getActivity()) + .setItems(new CharSequence[]{ + getString(R.string.group_context_message).replace("{0}", userVM.getName().get()), + getString(R.string.group_context_call).replace("{0}", userVM.getName().get()), + getString(R.string.group_context_view).replace("{0}", userVM.getName().get()), + getString(R.string.group_context_remove).replace("{0}", userVM.getName().get()), + }, (dialog, which) -> { + if (which == 0) { + startActivity(Intents.openPrivateDialog(userVM.getId(), true, getActivity())); + } else if (which == 1) { + final ArrayList phones = userVM.getPhones().get(); + if (phones.size() == 0) { + Toast.makeText(getActivity(), "No phones available", Toast.LENGTH_SHORT).show(); + } else if (phones.size() == 1) { + startActivity(Intents.call(phones.get(0).getPhone())); + } else { + CharSequence[] sequences = new CharSequence[phones.size()]; + for (int i = 0; i < sequences.length; i++) { + try { + Phonenumber.PhoneNumber number = PhoneNumberUtil.getInstance().parse("+" + phones.get(i).getPhone(), "us"); + sequences[i] = phones.get(which).getTitle() + ": " + PhoneNumberUtil.getInstance().format(number, PhoneNumberUtil.PhoneNumberFormat.INTERNATIONAL); + } catch (NumberParseException e) { + e.printStackTrace(); + sequences[i] = phones.get(which).getTitle() + ": +" + phones.get(i).getPhone(); + } + } + new AlertDialog.Builder(getActivity()) + .setItems(sequences, (dialog1, which1) -> { + startActivity(Intents.call(phones.get(which1).getPhone())); + }) + .show() + .setCanceledOnTouchOutside(true); + } + } else if (which == 2) { + ActorSDKLauncher.startProfileActivity(getActivity(), userVM.getId()); + } else if (which == 3) { + new AlertDialog.Builder(getActivity()) + .setMessage(getString(R.string.alert_group_remove_text).replace("{0}", userVM.getName().get())) + .setPositiveButton(R.string.alert_group_remove_yes, (dialog2, which1) -> { + execute(messenger().kickMember(chatId, userVM.getId()), + R.string.progress_common, new CommandCallback() { + @Override + public void onResult(Void res1) { - descriptionContainer.setVisibility((aboutVis || themeVis || finalIsAdmin) ? View.VISIBLE : View.GONE); - // themeDivider.setVisibility(((themeVis && aboutVis) || isAdmin) ? View.VISIBLE : View.GONE); + } + @Override + public void onError(Exception e) { + Toast.makeText(getActivity(), R.string.toast_unable_kick, Toast.LENGTH_SHORT).show(); + } + }); + }) + .setNegativeButton(R.string.dialog_cancel, null) + .show() + .setCanceledOnTouchOutside(true); + } + }) + .show() + .setCanceledOnTouchOutside(true); } public void updateBar(int offset) { @@ -420,7 +391,7 @@ public void updateBar(int offset) { @Override public void onCreateOptionsMenu(Menu menu, MenuInflater menuInflater) { super.onCreateOptionsMenu(menu, menuInflater); - if (groupInfo.isMember().get()) { + if (groupVM.isMember().get()) { menuInflater.inflate(R.menu.group_info, menu); } } @@ -430,17 +401,13 @@ public boolean onOptionsItemSelected(MenuItem item) { if (item.getItemId() == R.id.leaveGroup) { new AlertDialog.Builder(getActivity()) .setMessage(getString(R.string.alert_leave_group_message).replace("%1$s", - groupInfo.getName().get())) - .setPositiveButton(R.string.alert_leave_group_yes, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog2, int which) { - execute(messenger().leaveGroup(chatId)); - } + groupVM.getName().get())) + .setPositiveButton(R.string.alert_leave_group_yes, (dialog2, which) -> { + execute(messenger().leaveGroup(chatId)); }) .setNegativeButton(R.string.dialog_cancel, null) .show() .setCanceledOnTouchOutside(true); - return true; } else if (item.getItemId() == R.id.addMember) { startActivity(new Intent(getActivity(), AddMemberActivity.class) @@ -453,50 +420,13 @@ public void onClick(DialogInterface dialog2, int which) { return super.onOptionsItemSelected(item); } - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - if (resultCode == Activity.RESULT_OK && requestCode == 0 && data != null && data.hasExtra(Intents.EXTRA_UID)) { - final UserVM userModel = users().get(data.getIntExtra(Intents.EXTRA_UID, 0)); - - for (GroupMember uid : groupInfo.getMembers().get()) { - if (uid.getUid() == userModel.getId()) { - Toast.makeText(getActivity(), R.string.toast_already_member, Toast.LENGTH_SHORT).show(); - return; - } - } - - new AlertDialog.Builder(getActivity()) - .setMessage(getString(R.string.alert_group_add_text).replace("{0}", userModel.getName().get())) - .setPositiveButton(R.string.alert_group_add_yes, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog2, int which) { - execute(messenger().inviteMember(chatId, userModel.getId()), - R.string.progress_common, new CommandCallback() { - @Override - public void onResult(Void res) { - - } - - @Override - public void onError(Exception e) { - Toast.makeText(getActivity(), R.string.toast_unable_add, Toast.LENGTH_SHORT).show(); - } - }); - } - }) - .setNegativeButton(R.string.dialog_cancel, null) - .show() - .setCanceledOnTouchOutside(true); - } else { - super.onActivityResult(requestCode, resultCode, data); - } - } - @Override public void onDestroyView() { super.onDestroyView(); - groupUserAdapter.dispose(); - groupUserAdapter = null; + if (groupUserAdapter != null) { + groupUserAdapter.dispose(); + groupUserAdapter = null; + } if (avatarView != null) { avatarView.unbind(); avatarView = null; diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_group_header.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_group_header.xml index da08f4c975..7c5fa84bc2 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_group_header.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_group_header.xml @@ -125,14 +125,14 @@ android:id="@+id/about" android:layout_width="match_parent" android:layout_height="wrap_content" - android:textSize="18sp" /> + android:textSize="16sp" /> + android:textSize="14sp" /> diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-ru/ui_text.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-ru/ui_text.xml index 6be1c7cf53..645df4f8c5 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-ru/ui_text.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-ru/ui_text.xml @@ -323,8 +323,8 @@ админ. - Создан {0} - Создан Вами + Владелец {0} + Вы владелец Добавление в группу… Исключение из группы… diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml index d6463900dc..d8c0a8467c 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml @@ -333,8 +333,8 @@ admin - Created by {0} - Created by You + Owned by {0} + Owned by You Adding to group… Removing from group… diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java index 743e975f7c..1f088949e4 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java @@ -1528,6 +1528,19 @@ public Command makeAdmin(final int gid, final int uid) { .failure(e -> callback.onError(e)); } + /** + * Transfer ownership of group + * + * @param gid group's id + * @param uid user's id + * @return Promise of void + */ + @Nullable + @ObjectiveCName("transferOwnershipWithGid:withUid:") + public Promise transferOwnership(int gid, int uid) { + return modules.getGroupsModule().transferOwnership(gid, uid); + } + /** * Request invite link for group * diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsModule.java index f986db9634..9d824aacab 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsModule.java @@ -25,6 +25,7 @@ import im.actor.core.api.rpc.RequestMakeUserAdminObsolete; import im.actor.core.api.rpc.RequestRevokeIntegrationToken; import im.actor.core.api.rpc.RequestRevokeInviteUrl; +import im.actor.core.api.rpc.RequestTransferOwnership; import im.actor.core.api.rpc.ResponseIntegrationToken; import im.actor.core.api.rpc.ResponseInviteUrl; import im.actor.core.entity.Group; @@ -177,6 +178,14 @@ public Promise makeAdmin(final int gid, final int uid) { .flatMap(r -> updates().waitForUpdate(r.getSeq())); } + public Promise transferOwnership(final int gid, final int uid) { + return Promises.tuple(getGroups().getValueAsync(gid), users().getValueAsync(uid)) + .flatMap(groupUserTuple2 -> api(new RequestTransferOwnership( + new ApiGroupOutPeer(gid, groupUserTuple2.getT1().getAccessHash()), + uid))) + .flatMap(r -> updates().waitForUpdate(r.getSeq())); + } + public Promise editTitle(final int gid, final String name) { final long rid = RandomUtils.nextRid(); return getGroups().getValueAsync(gid) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java index 3b66955cf0..43bcf90ace 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java @@ -8,12 +8,10 @@ import com.google.j2objc.annotations.Property; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; -import java.util.concurrent.CopyOnWriteArrayList; import im.actor.core.entity.Group; import im.actor.core.entity.GroupMember; @@ -57,7 +55,7 @@ public class GroupVM extends BaseValueModel { private ValueModel> members; @Property("nonatomic, readonly") - private int creatorId; + private IntValueModel ownerId; @NotNull @Property("nonatomic, readonly") private ValueModel presence; @@ -69,7 +67,7 @@ public class GroupVM extends BaseValueModel { private StringValueModel about; @NotNull - private CopyOnWriteArrayList> listeners = new CopyOnWriteArrayList<>(); + private ArrayList> listeners = new ArrayList<>(); /** *

INTERNAL API

@@ -80,13 +78,13 @@ public class GroupVM extends BaseValueModel { public GroupVM(@NotNull Group rawObj) { super(rawObj); this.groupId = rawObj.getGroupId(); - this.creatorId = rawObj.getOwnerId(); this.name = new StringValueModel("group." + groupId + ".title", rawObj.getTitle()); this.avatar = new AvatarValueModel("group." + groupId + ".avatar", rawObj.getAvatar()); this.isMember = new BooleanValueModel("group." + groupId + ".isMember", rawObj.isMember()); this.membersCount = new IntValueModel("group." + groupId + ".membersCount", rawObj.getMembersCount()); this.isCanWriteMessage = new BooleanValueModel("group." + groupId + ".can_write", rawObj.isCanWrite()); + this.ownerId = new IntValueModel("group." + groupId + ".membersCount", rawObj.getOwnerId()); this.members = new ValueModel<>("group." + groupId + ".members", new HashSet<>(rawObj.getMembers())); this.presence = new ValueModel<>("group." + groupId + ".presence", 0); this.theme = new StringValueModel("group." + groupId + ".theme", rawObj.getTopic()); @@ -159,13 +157,13 @@ public BooleanValueModel getIsCanWriteMessage() { } /** - * Get Group creator user id + * Get Group owner user id model * - * @return creator user id + * @return creator owner id model */ - @ObjectiveCName("getCreatorId") - public int getCreatorId() { - return creatorId; + @ObjectiveCName("getCreatorIdModel") + public IntValueModel getOwnerId() { + return ownerId; } /** @@ -200,6 +198,7 @@ protected void updateValues(@NotNull Group rawObj) { isChanged |= theme.change(rawObj.getTopic()); isChanged |= about.change(rawObj.getAbout()); isChanged |= members.change(new HashSet<>(rawObj.getMembers())); + isChanged |= ownerId.change(rawObj.getOwnerId()); if (isChanged) { notifyChange(); diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/UserVM.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/UserVM.java index 83c4a02d9b..bd7b823406 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/UserVM.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/UserVM.java @@ -372,7 +372,7 @@ public ValueModelBotCommands getBotCommands() { @MainThread @ObjectiveCName("subscribeWithListener:") public void subscribe(@NotNull ModelChangedListener listener) { - Runtime.checkMainThread(); + // Runtime.checkMainThread(); if (listeners.contains(listener)) { return; } @@ -388,7 +388,7 @@ public void subscribe(@NotNull ModelChangedListener listener) { @MainThread @ObjectiveCName("subscribeWithListener:withNotify:") public void subscribe(@NotNull ModelChangedListener listener, boolean notify) { - Runtime.checkMainThread(); + // Runtime.checkMainThread(); if (listeners.contains(listener)) { return; } @@ -406,17 +406,14 @@ public void subscribe(@NotNull ModelChangedListener listener, boolean no @MainThread @ObjectiveCName("unsubscribeWithListener:") public void unsubscribe(@NotNull ModelChangedListener listener) { - Runtime.checkMainThread(); + // Runtime.checkMainThread(); listeners.remove(listener); } private void notifyChange() { - Runtime.postToMainThread(new Runnable() { - @Override - public void run() { - for (ModelChangedListener l : listeners.toArray(new ModelChangedListener[listeners.size()])) { - l.onChanged(UserVM.this); - } + Runtime.postToMainThread(() -> { + for (ModelChangedListener l : listeners) { + l.onChanged(UserVM.this); } }); } diff --git a/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/mvvm/Value.java b/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/mvvm/Value.java index ad5a6af1b5..07e26acdf5 100644 --- a/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/mvvm/Value.java +++ b/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/mvvm/Value.java @@ -11,7 +11,7 @@ */ public abstract class Value { - private ArrayList> listeners = new ArrayList>(); + private ArrayList> listeners = new ArrayList<>(); private String name; @@ -99,8 +99,7 @@ protected void notify(final T value) { * @param value new value */ protected void notifyInMainThread(final T value) { - for (ValueChangedListener listener : - listeners.toArray(new ValueChangedListener[listeners.size()])) { + for (ValueChangedListener listener : listeners) { listener.onChanged(value, Value.this); } } diff --git a/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/mvvm/ValueListener.java b/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/mvvm/ValueListener.java new file mode 100644 index 0000000000..8504df22e9 --- /dev/null +++ b/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/mvvm/ValueListener.java @@ -0,0 +1,12 @@ +/* + * Copyright (C) 2015 Actor LLC. + */ + +package im.actor.runtime.mvvm; + +import com.google.j2objc.annotations.ObjectiveCName; + +public interface ValueListener { + @ObjectiveCName("onChanged:") + void onChanged(T val); +} \ No newline at end of file From 208dd05e8b25aa4b2c3d12299a017b3414111cde Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Wed, 13 Jul 2016 09:21:00 +0300 Subject: [PATCH 011/414] perf(android): Speed up chat activity loading --- .../conversation/ChatActivity.java | 66 ++++++++- .../java/im/actor/sdk/view/ActorToolbar.java | 140 ++++++++++++++++++ 2 files changed, 204 insertions(+), 2 deletions(-) create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/ActorToolbar.java diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatActivity.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatActivity.java index ef30244fb0..fd6e8b0ce4 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatActivity.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatActivity.java @@ -2,6 +2,8 @@ import android.content.Context; import android.content.Intent; +import android.content.res.Configuration; +import android.content.res.TypedArray; import android.graphics.Color; import android.graphics.drawable.ColorDrawable; import android.os.Bundle; @@ -9,15 +11,26 @@ import android.support.v4.app.Fragment; import android.support.v7.view.ActionMode; import android.support.v7.widget.Toolbar; +import android.util.TypedValue; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; import im.actor.core.entity.Peer; +import im.actor.sdk.ActorSDK; import im.actor.sdk.R; import im.actor.sdk.controllers.activity.BaseActivity; +import im.actor.sdk.util.Screen; +import im.actor.sdk.view.ActorToolbar; public class ChatActivity extends BaseActivity { public static final String EXTRA_CHAT_PEER = "chat_peer"; + private Toolbar toolbar; + public static Intent build(Peer peer, Context context) { final Intent intent = new Intent(context, ChatActivity.class); intent.putExtra(EXTRA_CHAT_PEER, peer.getUnuqueId()); @@ -28,18 +41,57 @@ public static Intent build(Peer peer, Context context) { public void onCreate(Bundle saveInstance) { super.onCreate(saveInstance); + // // For faster keyboard open/close + // + getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); // // Loading Layout // - setContentView(R.layout.activity_dialog); - setSupportActionBar((Toolbar) findViewById(R.id.toolbar)); + + RelativeLayout rootLayout = new RelativeLayout(this); + View antiFocus = new View(this); + antiFocus.setLayoutParams(new RelativeLayout.LayoutParams(0, 0)); + antiFocus.setFocusable(true); + antiFocus.setFocusableInTouchMode(true); + rootLayout.addView(antiFocus); + + FrameLayout chatFragmentCont = new FrameLayout(this); + chatFragmentCont.setId(R.id.chatFragment); + RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT); + params.addRule(RelativeLayout.BELOW, R.id.toolbar); + chatFragmentCont.setLayoutParams(params); + rootLayout.addView(chatFragmentCont); + + ActorToolbar toolbar = new ActorToolbar(this); + // Toolbar toolbar = new Toolbar(this); + final TypedArray styledAttributes = getTheme().obtainStyledAttributes(new int[]{R.attr.actionBarSize}); + int actionBarSize = (int) styledAttributes.getDimension(0, 0); + styledAttributes.recycle(); + toolbar.setLayoutParams(new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, actionBarSize)); + toolbar.setMinimumHeight(actionBarSize); + toolbar.setId(R.id.toolbar); + toolbar.setBackgroundColor(ActorSDK.sharedActor().style.getToolBarColor()); + toolbar.setItemColor(Color.WHITE); + rootLayout.addView(toolbar); + this.toolbar = toolbar; + + FrameLayout overlay = new FrameLayout(this); + overlay.setId(R.id.overlay); + overlay.setLayoutParams(new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT)); + rootLayout.addView(overlay); + + setContentView(rootLayout); + setSupportActionBar(toolbar); // // Loading Fragments if needed // + if (saveInstance == null) { Peer peer = Peer.fromUniqueId(getIntent().getExtras().getLong(EXTRA_CHAT_PEER)); ChatFragment chatFragment = ChatFragment.create(peer); @@ -53,6 +105,16 @@ public void onCreate(Bundle saveInstance) { } } + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + + final TypedArray styledAttributes = getTheme().obtainStyledAttributes(new int[]{R.attr.actionBarSize}); + int actionBarSize = (int) styledAttributes.getDimension(0, 0); + styledAttributes.recycle(); + toolbar.setLayoutParams(new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, actionBarSize)); + toolbar.setMinimumHeight(actionBarSize); + } @Override public void onBackPressed() { diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/ActorToolbar.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/ActorToolbar.java new file mode 100644 index 0000000000..410176e154 --- /dev/null +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/ActorToolbar.java @@ -0,0 +1,140 @@ +package im.actor.sdk.view; + +import android.app.Activity; +import android.content.Context; +import android.graphics.ColorFilter; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.support.v7.view.menu.ActionMenuItemView; +import android.support.v7.widget.ActionMenuView; +import android.support.v7.widget.Toolbar; +import android.util.AttributeSet; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AutoCompleteTextView; +import android.widget.EditText; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.TextView; + +public class ActorToolbar extends Toolbar { + + public ActorToolbar(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + // TODO Auto-generated constructor stub + } + + public ActorToolbar(Context context, AttributeSet attrs) { + super(context, attrs); + // TODO Auto-generated constructor stub + } + + public ActorToolbar(Context context) { + super(context); + // TODO Auto-generated constructor stub + ctxt = context; + } + + int itemColor; + Context ctxt; + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + Log.d("LL", "onLayout"); + super.onLayout(changed, l, t, r, b); + colorizeToolbar(this, itemColor, (Activity) ctxt); + } + + public void setItemColor(int color) { + itemColor = color; + colorizeToolbar(this, itemColor, (Activity) ctxt); + } + + + /** + * Use this method to colorize toolbar icons to the desired target color + * + * @param toolbarView toolbar view being colored + * @param toolbarIconsColor the target color of toolbar icons + * @param activity reference to activity needed to register observers + */ + public static void colorizeToolbar(Toolbar toolbarView, int toolbarIconsColor, Activity activity) { + final PorterDuffColorFilter colorFilter + = new PorterDuffColorFilter(toolbarIconsColor, PorterDuff.Mode.SRC_IN); + + for (int i = 0; i < toolbarView.getChildCount(); i++) { + final View v = toolbarView.getChildAt(i); + + doColorizing(v, colorFilter, toolbarIconsColor); + } + + //Step 3: Changing the color of title and subtitle. + toolbarView.setTitleTextColor(toolbarIconsColor); + toolbarView.setSubtitleTextColor(toolbarIconsColor); + } + + public static void doColorizing(View v, final ColorFilter colorFilter, int toolbarIconsColor) { + if (v instanceof ImageButton) { + ((ImageButton) v).getDrawable().setAlpha(255); + ((ImageButton) v).getDrawable().setColorFilter(colorFilter); + } + + if (v instanceof ImageView) { + ((ImageView) v).getDrawable().setAlpha(255); + ((ImageView) v).getDrawable().setColorFilter(colorFilter); + } + + if (v instanceof AutoCompleteTextView) { + ((AutoCompleteTextView) v).setTextColor(toolbarIconsColor); + } + + if (v instanceof TextView) { + ((TextView) v).setTextColor(toolbarIconsColor); + } + + if (v instanceof EditText) { + ((EditText) v).setTextColor(toolbarIconsColor); + } + + if (v instanceof ViewGroup) { + for (int lli = 0; lli < ((ViewGroup) v).getChildCount(); lli++) { + doColorizing(((ViewGroup) v).getChildAt(lli), colorFilter, toolbarIconsColor); + } + } + + if (v instanceof ActionMenuView) { + for (int j = 0; j < ((ActionMenuView) v).getChildCount(); j++) { + + //Step 2: Changing the color of any ActionMenuViews - icons that + //are not back button, nor text, nor overflow menu icon. + final View innerView = ((ActionMenuView) v).getChildAt(j); + + if (innerView instanceof ActionMenuItemView) { + int drawablesCount = ((ActionMenuItemView) innerView).getCompoundDrawables().length; + for (int k = 0; k < drawablesCount; k++) { + if (((ActionMenuItemView) innerView).getCompoundDrawables()[k] != null) { + final int finalK = k; + + //Important to set the color filter in seperate thread, + //by adding it to the message queue + //Won't work otherwise. + //Works fine for my case but needs more testing + + ((ActionMenuItemView) innerView).getCompoundDrawables()[finalK].setColorFilter(colorFilter); + +// innerView.post(new Runnable() { +// @Override +// public void run() { +// ((ActionMenuItemView) innerView).getCompoundDrawables()[finalK].setColorFilter(colorFilter); +// } +// }); + } + } + } + } + } + } + + +} From 13f6c7c9993dbcad2642c54eef24c576646d0219 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Wed, 13 Jul 2016 09:21:32 +0300 Subject: [PATCH 012/414] fix(android): Fixing GroupInfoFragment's up button --- .../java/im/actor/sdk/controllers/group/GroupInfoFragment.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java index f014ede688..6e70f70d48 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java @@ -76,6 +76,8 @@ public static GroupInfoFragment create(int chatId) { public void onCreate(Bundle saveInstance) { super.onCreate(saveInstance); setRootFragment(true); + setHomeAsUp(true); + setShowHome(true); } @Override From ee9b890187be204ce492171b14cdd01ae5be1094 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Wed, 13 Jul 2016 10:02:06 +0300 Subject: [PATCH 013/414] perf(android): Speed up root fragment loading --- .../sdk/controllers/root/RootFragment.java | 51 +++++++++++++++++-- .../src/main/res/layout/activity_root.xml | 13 +++-- .../main/res/layout/activity_root_content.xml | 12 ++--- 3 files changed, 59 insertions(+), 17 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/root/RootFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/root/RootFragment.java index ed1057c71b..88624562e5 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/root/RootFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/root/RootFragment.java @@ -9,6 +9,7 @@ import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import android.widget.FrameLayout; import im.actor.sdk.ActorSDK; import im.actor.sdk.R; @@ -28,13 +29,51 @@ public RootFragment() { setTitle(ActorSDK.sharedActor().getAppName()); } + private boolean isInited = false; + + @Override + public void onCreate(Bundle saveInstance) { + super.onCreate(saveInstance); + if (saveInstance != null) { + isInited = saveInstance.getBoolean("is_inited"); + } + } + @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - View res = inflater.inflate(R.layout.activity_root_content, container, false); + FrameLayout res = new FrameLayout(getContext()); - if (savedInstanceState == null) { + FrameLayout content = new FrameLayout(getContext()); + content.setId(R.id.content); + res.addView(content, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT)); + + FrameLayout fab = new FrameLayout(getContext()); + fab.setId(R.id.fab); + res.addView(fab, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT)); + + FrameLayout search = new FrameLayout(getContext()); + search.setId(R.id.search); + res.addView(search, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT)); + + FrameLayout placeholder = new FrameLayout(getContext()); + placeholder.setId(R.id.placeholder); + res.addView(placeholder, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT)); + + return res; + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + if (!isInited) { + isInited = true; getChildFragmentManager().beginTransaction() .add(R.id.content, new DialogsDefaultFragment()) .add(R.id.fab, new ComposeFabFragment()) @@ -42,8 +81,6 @@ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, .add(R.id.placeholder, new GlobalPlaceholderFragment()) .commit(); } - - return res; } @Override @@ -68,4 +105,10 @@ public boolean onOptionsItemSelected(MenuItem item) { return super.onOptionsItemSelected(item); } } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putBoolean("is_inited", isInited); + } } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/activity_root.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/activity_root.xml index 89a0a9b26e..adf9a08d9e 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/activity_root.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/activity_root.xml @@ -10,6 +10,12 @@ android:layout_height="match_parent" android:orientation="vertical"> + + - - + android:layout_height="match_parent" /> \ No newline at end of file diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/activity_root_content.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/activity_root_content.xml index 7e59728433..f3b0be42e4 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/activity_root_content.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/activity_root_content.xml @@ -7,24 +7,20 @@ + android:layout_height="match_parent" /> + android:layout_height="match_parent" /> + android:layout_height="match_parent" /> + android:layout_height="match_parent" /> From 9db9d39041ba97080c780c77720d384838f70b8d Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Wed, 13 Jul 2016 10:18:09 +0300 Subject: [PATCH 014/414] perf(android): Lazy initialization of attach menu --- .../conversation/attach/AttachFragment.java | 33 ++++++++++++++++--- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/attach/AttachFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/attach/AttachFragment.java index 7adda58b83..64c4262c3b 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/attach/AttachFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/attach/AttachFragment.java @@ -49,11 +49,14 @@ public class AttachFragment extends AbsAttachFragment implements MediaPickerCall private static final int PERMISSION_REQ_MEDIA = 11; + private FrameLayout root; private View container; private FastAttachAdapter fastAttachAdapter; private ImageView menuIconToChange; private TextView menuTitleToChange; + private boolean isLoaded = false; + public AttachFragment(Peer peer) { super(peer); } @@ -66,14 +69,29 @@ public AttachFragment() { public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup fcontainer, @Nullable Bundle savedInstanceState) { if (savedInstanceState == null) { - // Adding Media Picker getChildFragmentManager().beginTransaction() .add(new MediaPickerFragment(), "picker") .commitNow(); } - container = getLayoutInflater(null).inflate(R.layout.share_menu, fcontainer, false); + root = new FrameLayout(getContext()); + root.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT)); + + isLoaded = false; + + return root; + } + + private void prepareView() { + if (isLoaded) { + return; + } + isLoaded = true; + + container = getLayoutInflater(null).inflate(R.layout.share_menu, root, false); container.setVisibility(View.INVISIBLE); + container.findViewById(R.id.menu_bg).setBackgroundColor(style.getMainBackgroundColor()); container.findViewById(R.id.cancelField).setOnClickListener(view -> hide()); @@ -195,11 +213,12 @@ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup fcontainer } }); - return container; + root.addView(container); } @Override public void show() { + prepareView(); if (container.getVisibility() == View.INVISIBLE) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { Activity activity = getActivity(); @@ -231,7 +250,7 @@ public void show() { @Override public void hide() { - if (container.getVisibility() == View.VISIBLE) { + if (container != null && container.getVisibility() == View.VISIBLE) { onHidden(); fastAttachAdapter.clearSelected(); messenger().getGalleryScannerActor().send(new GalleryScannerActor.Hide()); @@ -355,7 +374,7 @@ public void onLocationPicked(double latitude, double longitude, String street, S @Override public boolean onBackPressed() { - if (container.getVisibility() == View.VISIBLE) { + if (container != null && container.getVisibility() == View.VISIBLE) { hide(); return true; } @@ -385,5 +404,9 @@ public void onDestroyView() { fastAttachAdapter.release(); fastAttachAdapter = null; } + container = null; + root = null; + menuIconToChange = null; + menuTitleToChange = null; } } From b6578db93c103112ecf1401764d4f15c69bb4891 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Wed, 13 Jul 2016 10:33:30 +0300 Subject: [PATCH 015/414] feat(android): Sorting members --- .../controllers/group/GroupInfoFragment.java | 4 ++-- .../group/view/MembersAdapter.java | 24 ++++++++++++++----- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java index 6e70f70d48..1ee750502a 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java @@ -232,9 +232,9 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa // Members // - groupUserAdapter = new MembersAdapter(groupVM.getMembers().get(), getActivity()); + groupUserAdapter = new MembersAdapter(getActivity()); bind(groupVM.getMembers(), members -> { - groupUserAdapter.updateUid(members); + groupUserAdapter.setMembers(members); }); listView.setAdapter(groupUserAdapter); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/view/MembersAdapter.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/view/MembersAdapter.java index ad03b224e9..25718da36c 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/view/MembersAdapter.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/view/MembersAdapter.java @@ -6,7 +6,9 @@ import android.view.ViewGroup; import android.widget.TextView; +import java.util.Arrays; import java.util.Collection; +import java.util.Comparator; import im.actor.sdk.ActorSDK; import im.actor.sdk.R; @@ -21,17 +23,27 @@ import static im.actor.sdk.util.ActorSDKMessenger.users; public class MembersAdapter extends HolderAdapter { - private GroupMember[] members; - private ActorBinder BINDER = new ActorBinder(); + private GroupMember[] members = new GroupMember[0]; + private ActorBinder BINDER = new ActorBinder(); - public MembersAdapter(Collection members, Context context) { + public MembersAdapter(Context context) { super(context); - this.members = members.toArray(new GroupMember[0]); } - public void updateUid(Collection members) { - this.members = members.toArray(new GroupMember[0]); + public void setMembers(Collection members) { + this.members = members.toArray(new GroupMember[members.size()]); + Arrays.sort(this.members, (a, b) -> { + if (a.isAdministrator() && !b.isAdministrator()) { + return -1; + } + if (b.isAdministrator() && !a.isAdministrator()) { + return 1; + } + String an = users().get(a.getInviterUid()).getName().get(); + String bn = users().get(b.getInviterUid()).getName().get(); + return an.compareTo(bn); + }); notifyDataSetChanged(); } From 8af0276a83beae88d48b647023cd0e00ef67a236 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Wed, 13 Jul 2016 17:45:28 +0300 Subject: [PATCH 016/414] fix(android): fix group info fragment init --- .../im/actor/sdk/controllers/group/GroupInfoFragment.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java index 1ee750502a..6ca5670e0b 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java @@ -72,9 +72,7 @@ public static GroupInfoFragment create(int chatId) { private MembersAdapter groupUserAdapter; private View notMemberView; - @Override - public void onCreate(Bundle saveInstance) { - super.onCreate(saveInstance); + public GroupInfoFragment() { setRootFragment(true); setHomeAsUp(true); setShowHome(true); From 044553ab43f76bc9369da3574cacc10833d53094 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Wed, 13 Jul 2016 17:50:45 +0300 Subject: [PATCH 017/414] fix(scheme): Fixed missed user out peer on transfer ownership --- actor-sdk/sdk-api/actor.json | 4 ++-- .../solutions/im.actor.api/models/im/actor/api/scheme.mps | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/actor-sdk/sdk-api/actor.json b/actor-sdk/sdk-api/actor.json index 0b3f29507d..46457c3d4c 100644 --- a/actor-sdk/sdk-api/actor.json +++ b/actor-sdk/sdk-api/actor.json @@ -10017,8 +10017,8 @@ }, { "type": { - "type": "alias", - "childType": "userId" + "type": "struct", + "childType": "UserOutPeer" }, "id": 2, "name": "newOwner" diff --git a/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps b/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps index 71bfaf3c3a..04be18fc4a 100644 --- a/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps +++ b/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps @@ -8697,8 +8697,8 @@ - - + + From ffd84704f0ac535da5c1d6b7c76de225e8f7e684 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Wed, 13 Jul 2016 17:50:45 +0300 Subject: [PATCH 018/414] fix(scheme): Fixed missed user out peer on transfer ownership --- actor-sdk/sdk-api/actor.json | 4 ++-- .../solutions/im.actor.api/models/im/actor/api/scheme.mps | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/actor-sdk/sdk-api/actor.json b/actor-sdk/sdk-api/actor.json index ef48ff2e50..4c358dbec1 100644 --- a/actor-sdk/sdk-api/actor.json +++ b/actor-sdk/sdk-api/actor.json @@ -10011,8 +10011,8 @@ }, { "type": { - "type": "alias", - "childType": "userId" + "type": "struct", + "childType": "UserOutPeer" }, "id": 2, "name": "newOwner" diff --git a/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps b/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps index be0622f54c..05a8d5d084 100644 --- a/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps +++ b/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps @@ -8697,8 +8697,8 @@ - - + + From 4ddad540e2766308cbd4f8dc113abe1d2d08e51f Mon Sep 17 00:00:00 2001 From: rockjam Date: Wed, 13 Jul 2016 22:48:55 +0300 Subject: [PATCH 019/414] fix(server:rpc): add access hash check for user on transfer ownership --- .../actor-core/src/main/actor-api/actor.json | 4 +- .../service/groups/GroupsServiceImpl.scala | 38 +++++++++++-------- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/actor-server/actor-core/src/main/actor-api/actor.json b/actor-server/actor-core/src/main/actor-api/actor.json index ef48ff2e50..4c358dbec1 100644 --- a/actor-server/actor-core/src/main/actor-api/actor.json +++ b/actor-server/actor-core/src/main/actor-api/actor.json @@ -10011,8 +10011,8 @@ }, { "type": { - "type": "alias", - "childType": "userId" + "type": "struct", + "childType": "UserOutPeer" }, "id": 2, "name": "newOwner" diff --git a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupsServiceImpl.scala b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupsServiceImpl.scala index 2256681533..b83512acb5 100644 --- a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupsServiceImpl.scala +++ b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupsServiceImpl.scala @@ -71,9 +71,11 @@ final class GroupsServiceImpl(groupInviteConfig: GroupInviteConfig)(implicit act override protected def doHandleMakeUserAdmin(groupPeer: ApiGroupOutPeer, userPeer: ApiUserOutPeer, clientData: ClientData): Future[HandlerResult[ResponseSeqDate]] = { authorized(clientData) { implicit client ⇒ withGroupOutPeer(groupPeer) { - for { - (_, SeqStateDate(seq, state, date)) ← groupExt.makeUserAdmin(groupPeer.groupId, client.userId, client.authId, userPeer.userId) - } yield Ok(ResponseSeqDate(seq, state.toByteArray, date)) + withUserOutPeer(userPeer) { + for { + (_, SeqStateDate(seq, state, date)) ← groupExt.makeUserAdmin(groupPeer.groupId, client.userId, client.authId, userPeer.userId) + } yield Ok(ResponseSeqDate(seq, state.toByteArray, date)) + } } } } @@ -108,12 +110,14 @@ final class GroupsServiceImpl(groupInviteConfig: GroupInviteConfig)(implicit act * @param newOwner New group's owner */ //TODO: figure out what date should be - override protected def doHandleTransferOwnership(groupPeer: ApiGroupOutPeer, newOwner: Int, clientData: ClientData): Future[HandlerResult[ResponseSeqDate]] = + override protected def doHandleTransferOwnership(groupPeer: ApiGroupOutPeer, newOwner: ApiUserOutPeer, clientData: ClientData): Future[HandlerResult[ResponseSeqDate]] = authorized(clientData) { implicit client ⇒ withGroupOutPeer(groupPeer) { - for { - SeqState(seq, state) ← groupExt.transferOwnership(groupPeer.groupId, client.userId, client.authId, newOwner) - } yield Ok(ResponseSeqDate(seq, state.toByteArray, Instant.now.toEpochMilli)) + withUserOutPeer(newOwner) { + for { + SeqState(seq, state) ← groupExt.transferOwnership(groupPeer.groupId, client.userId, client.authId, newOwner.userId) + } yield Ok(ResponseSeqDate(seq, state.toByteArray, Instant.now.toEpochMilli)) + } } } @@ -183,11 +187,13 @@ final class GroupsServiceImpl(groupInviteConfig: GroupInviteConfig)(implicit act authorized(clientData) { implicit client ⇒ addOptimizations(optimizations) withGroupOutPeer(groupPeer) { - for { - SeqStateDate(seq, state, date) ← groupExt.kickUser(groupPeer.groupId, userOutPeer.userId, randomId) - } yield { - groupPresenceExt.notifyGroupUserRemoved(groupPeer.groupId, userOutPeer.userId) - Ok(ResponseSeqDate(seq, state.toByteArray, date)) + withUserOutPeer(userOutPeer) { + for { + SeqStateDate(seq, state, date) ← groupExt.kickUser(groupPeer.groupId, userOutPeer.userId, randomId) + } yield { + groupPresenceExt.notifyGroupUserRemoved(groupPeer.groupId, userOutPeer.userId) + Ok(ResponseSeqDate(seq, state.toByteArray, date)) + } } } } @@ -498,9 +504,11 @@ final class GroupsServiceImpl(groupInviteConfig: GroupInviteConfig)(implicit act ): Future[HandlerResult[ResponseMakeUserAdminObsolete]] = { authorized(clientData) { implicit client ⇒ withGroupOutPeer(groupPeer) { - for { - (members, SeqStateDate(seq, state, _)) ← groupExt.makeUserAdmin(groupPeer.groupId, client.userId, client.authId, userPeer.userId) - } yield Ok(ResponseMakeUserAdminObsolete(members, seq, state.toByteArray)) + withUserOutPeer(userPeer) { + for { + (members, SeqStateDate(seq, state, _)) ← groupExt.makeUserAdmin(groupPeer.groupId, client.userId, client.authId, userPeer.userId) + } yield Ok(ResponseMakeUserAdminObsolete(members, seq, state.toByteArray)) + } } } } From f43d3717f5d6586e51268a78885e14fc8811469e Mon Sep 17 00:00:00 2001 From: rockjam Date: Wed, 13 Jul 2016 22:56:57 +0300 Subject: [PATCH 020/414] fix(server): make possible to set empty about and topic for group --- .../im/actor/server/group/GroupCommandHandlers.scala | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala index d11c8c3269..9fa5cb7a14 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala @@ -678,9 +678,9 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { } protected def updateTopic(cmd: UpdateTopic): Unit = { - def isValidTopic(topic: Option[String]) = topic.forall(t ⇒ t.nonEmpty && t.length < 255) + def isValidTopic(topic: Option[String]) = topic.forall(_.length < 255) - val topic = cmd.topic map (_.trim) + val topic = trimToEmpty(cmd.topic) if (state.nonMember(cmd.clientUserId)) { sender() ! notMember @@ -748,9 +748,9 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { } protected def updateAbout(cmd: UpdateAbout): Unit = { - def isValidAbout(about: Option[String]) = about.forall(a ⇒ a.nonEmpty && a.length < 255) + def isValidAbout(about: Option[String]) = about.forall(_.length < 255) - val about = cmd.about map (_.trim) + val about = trimToEmpty(cmd.about) if (!state.isAdmin(cmd.clientUserId)) { sender() ! notAdmin @@ -915,6 +915,9 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { } } + private def trimToEmpty(s: Option[String]): Option[String] = + s map (_.trim) filter (_.nonEmpty) + private def getAvatarData(avatar: Option[Avatar]): AvatarData = avatar .map(ImageUtils.getAvatarData(AvatarData.OfGroup, groupId, _)) From 8a2970b04840aad83f752ab2142e9e4efe8c01a6 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Thu, 14 Jul 2016 10:27:50 +0300 Subject: [PATCH 021/414] wip(code+android): Working on creation of channels --- .../compose/GroupNameFragment.java | 37 ++++++++-------- .../compose/GroupUsersFragment.java | 22 ++++------ .../conversation/ChatFragment.java | 13 ++++-- .../main/java/im/actor/core/Messenger.java | 44 ++++++++++++------- .../core/modules/groups/GroupsModule.java | 14 +++++- .../java/im/actor/core/viewmodel/GroupVM.java | 17 +++++++ 6 files changed, 93 insertions(+), 54 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/compose/GroupNameFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/compose/GroupNameFragment.java index e20fb1821e..874554f49a 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/compose/GroupNameFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/compose/GroupNameFragment.java @@ -2,6 +2,7 @@ import android.app.Activity; import android.content.Intent; +import android.net.Uri; import android.os.Bundle; import android.view.KeyEvent; import android.view.LayoutInflater; @@ -14,17 +15,20 @@ import android.widget.EditText; import android.widget.TextView; +import java.util.List; + +import im.actor.runtime.function.Consumer; import im.actor.sdk.ActorSDK; import im.actor.sdk.R; import im.actor.sdk.controllers.Intents; import im.actor.sdk.controllers.BaseFragment; +import im.actor.sdk.controllers.tools.MediaPickerCallback; import im.actor.sdk.util.Screen; import im.actor.sdk.view.avatar.AvatarView; import im.actor.sdk.util.KeyboardHelper; -/** - * Created by ex3ndr on 04.10.14. - */ +import static im.actor.sdk.util.ActorSDKMessenger.messenger; + public class GroupNameFragment extends BaseFragment { private static final int REQUEST_AVATAR = 1; @@ -50,15 +54,12 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa res.setBackgroundColor(ActorSDK.sharedActor().style.getMainBackgroundColor()); ((TextView) res.findViewById(R.id.create_group_hint)).setTextColor(ActorSDK.sharedActor().style.getTextSecondaryColor()); groupName = (EditText) res.findViewById(R.id.groupTitle); - groupName.setOnEditorActionListener(new TextView.OnEditorActionListener() { - @Override - public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { - if (actionId == EditorInfo.IME_ACTION_NEXT) { - next(); - return true; - } - return false; + groupName.setOnEditorActionListener((v, actionId, event) -> { + if (actionId == EditorInfo.IME_ACTION_NEXT) { + next(); + return true; } + return false; }); groupName.setTextColor(ActorSDK.sharedActor().style.getTextPrimaryColor()); groupName.setHintTextColor(ActorSDK.sharedActor().style.getTextHintColor()); @@ -69,11 +70,8 @@ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { // avatarView.getHierarchy().setControllerOverlay(getResources().getDrawable(R.drawable.circle_selector)); avatarView.setImageURI(null); - res.findViewById(R.id.pickAvatar).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - startActivityForResult(Intents.pickAvatar(avatarPath != null, getActivity()), REQUEST_AVATAR); - } + res.findViewById(R.id.pickAvatar).setOnClickListener(view -> { + startActivityForResult(Intents.pickAvatar(avatarPath != null, getActivity()), REQUEST_AVATAR); }); return res; @@ -104,8 +102,11 @@ public boolean onOptionsItemSelected(MenuItem item) { private void next() { String title = groupName.getText().toString().trim(); if (title.length() > 0) { - ((CreateGroupActivity) getActivity()).showNextFragment( - GroupUsersFragment.create(groupName.getText().toString().trim(), avatarPath), false, true); +// ((CreateGroupActivity) getActivity()).showNextFragment( +// GroupUsersFragment.create(groupName.getText().toString().trim(), avatarPath), false, true); + messenger().createChannel(groupName.getText().toString().trim(), avatarPath).then(gid -> { + getActivity().finish(); + }); } } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/compose/GroupUsersFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/compose/GroupUsersFragment.java index 4141245c09..db087812e0 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/compose/GroupUsersFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/compose/GroupUsersFragment.java @@ -18,6 +18,7 @@ import im.actor.core.entity.Contact; import im.actor.core.viewmodel.CommandCallback; +import im.actor.runtime.function.Consumer; import im.actor.sdk.ActorSDK; import im.actor.sdk.R; import im.actor.sdk.controllers.Intents; @@ -106,20 +107,13 @@ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { public boolean onOptionsItemSelected(MenuItem item) { if (item.getItemId() == R.id.done) { if (getSelectedCount() > 0) { - execute(messenger().createGroup(title, avatarPath, BoxUtil.unbox(getSelected())), - R.string.progress_common, new CommandCallback() { - @Override - public void onResult(Integer res) { - getActivity().startActivity(Intents.openGroupDialog(res, true, getActivity())); - getActivity().finish(); - } - - @Override - public void onError(Exception e) { - Toast.makeText(getActivity(), getString(R.string.toast_unable_create_group), Toast.LENGTH_LONG).show(); - - } - }); + execute(messenger().createGroup(title, avatarPath, BoxUtil.unbox(getSelected())).then(gid -> { + getActivity().startActivity(Intents.openGroupDialog(gid, true, getActivity())); + getActivity().finish(); + }).failure(e -> { + Toast.makeText(getActivity(), getString(R.string.toast_unable_create_group), + Toast.LENGTH_LONG).show(); + })); } return true; } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatFragment.java index a05dcf54a7..ea7ca53091 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatFragment.java @@ -10,6 +10,7 @@ import android.widget.TextView; import android.widget.Toast; +import im.actor.core.entity.GroupType; import im.actor.core.entity.Peer; import im.actor.core.entity.PeerType; import im.actor.core.entity.Sticker; @@ -20,6 +21,7 @@ import im.actor.runtime.mvvm.Value; import im.actor.runtime.mvvm.ValueChangedListener; import im.actor.runtime.mvvm.ValueDoubleChangedListener; +import im.actor.runtime.mvvm.ValueListener; import im.actor.sdk.ActorSDK; import im.actor.sdk.ActorSDKLauncher; import im.actor.sdk.R; @@ -167,11 +169,16 @@ public void onResume() { } } else if (peer.getPeerType() == PeerType.GROUP) { GroupVM groupVM = groups().get(peer.getPeerId()); - - bind(groupVM.isMember(), (val, valueModel) -> { - if (val) { + bind(groupVM.isMember(), groupVM.getIsCanWriteMessage(), (isMember, valueModel, canWriteMessage, valueModel2) -> { + if (canWriteMessage) { goneView(inputOverlayContainer, false); showView(inputContainer, false); + } else if (isMember) { + inputOverlayText.setText("Can't send message"); + inputOverlayText.setTextColor(style.getListActionColor()); + inputOverlayText.setClickable(false); + showView(inputOverlayContainer, false); + goneView(inputContainer, false); } else { inputOverlayText.setText(R.string.chat_not_member); inputOverlayText.setTextColor(style.getListActionColor()); diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java index 1f088949e4..f7934323c7 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java @@ -1388,7 +1388,7 @@ public Command editName(final int uid, final String name) { * @param title new group title * @return Command for execution */ - @Nullable + @NotNull @ObjectiveCName("editGroupTitleCommandWithGid:withTitle:") public Command editGroupTitle(final int gid, final String title) { return callback -> modules.getGroupsModule().editTitle(gid, title) @@ -1458,16 +1458,26 @@ public void removeGroupAvatar(int gid) { * @param title group title * @param avatarDescriptor descriptor of group avatar (can be null if not set) * @param uids member's ids - * @return Command for execution + * @return Promise of group id */ - @Nullable - @ObjectiveCName("createGroupCommandWithTitle:withAvatar:withUids:") - public Command createGroup(String title, String avatarDescriptor, int[] uids) { - return callback -> modules.getGroupsModule().createGroup(title, avatarDescriptor, uids) - .then(integer -> callback.onResult(integer)) - .failure(e -> callback.onError(e)); + @NotNull + @ObjectiveCName("createGroupWithTitle:withAvatar:withUids:") + public Promise createGroup(String title, String avatarDescriptor, int[] uids) { + return modules.getGroupsModule().createGroup(title, avatarDescriptor, uids); } + /** + * Create channel + * + * @param title channel title + * @param avatarDescriptor descriptor of channel avatar (can be null if not set) + * @return Promise of channel id + */ + @NotNull + @ObjectiveCName("createChannelWithTitle:withAvatar:") + public Promise createChannel(String title, String avatarDescriptor) { + return modules.getGroupsModule().createChannel(title, avatarDescriptor); + } /** * Leave group @@ -1475,7 +1485,7 @@ public Command createGroup(String title, String avatarDescriptor, int[] * @param gid group's id * @return Command for execution */ - @Nullable + @NotNull @ObjectiveCName("leaveGroupCommandWithGid:") public Command leaveGroup(final int gid) { return callback -> modules.getGroupsModule().leaveGroup(gid) @@ -1490,7 +1500,7 @@ public Command leaveGroup(final int gid) { * @param uid user's id * @return Command for execution */ - @Nullable + @NotNull @ObjectiveCName("inviteMemberCommandWithGid:withUid:") public Command inviteMember(int gid, int uid) { return callback -> modules.getGroupsModule().addMember(gid, uid) @@ -1505,7 +1515,7 @@ public Command inviteMember(int gid, int uid) { * @param uid user's id * @return Command for execution */ - @Nullable + @NotNull @ObjectiveCName("kickMemberCommandWithGid:withUid:") public Command kickMember(int gid, int uid) { return callback -> modules.getGroupsModule().kickMember(gid, uid) @@ -1520,7 +1530,7 @@ public Command kickMember(int gid, int uid) { * @param uid user's id * @return Command for execution */ - @Nullable + @NotNull @ObjectiveCName("makeAdminCommandWithGid:withUid:") public Command makeAdmin(final int gid, final int uid) { return callback -> modules.getGroupsModule().makeAdmin(gid, uid) @@ -1535,7 +1545,7 @@ public Command makeAdmin(final int gid, final int uid) { * @param uid user's id * @return Promise of void */ - @Nullable + @NotNull @ObjectiveCName("transferOwnershipWithGid:withUid:") public Promise transferOwnership(int gid, int uid) { return modules.getGroupsModule().transferOwnership(gid, uid); @@ -1547,7 +1557,7 @@ public Promise transferOwnership(int gid, int uid) { * @param gid group's id * @return Command for execution */ - @Nullable + @NotNull @ObjectiveCName("requestInviteLinkCommandWithGid:") public Command requestInviteLink(int gid) { return callback -> modules.getGroupsModule().requestInviteLink(gid) @@ -1561,7 +1571,7 @@ public Command requestInviteLink(int gid) { * @param gid group's id * @return Command for execution */ - @Nullable + @NotNull @ObjectiveCName("requestRevokeLinkCommandWithGid:") public Command revokeInviteLink(int gid) { return callback -> modules.getGroupsModule().requestRevokeLink(gid) @@ -1575,7 +1585,7 @@ public Command revokeInviteLink(int gid) { * @param token invite token * @return Command for execution */ - @Nullable + @NotNull @ObjectiveCName("joinGroupViaLinkCommandWithToken:") public Command joinGroupViaToken(String token) { return callback -> modules.getGroupsModule().joinGroupByToken(token) @@ -1589,7 +1599,7 @@ public Command joinGroupViaToken(String token) { * @param gid group's id * @return Command for execution */ - @Nullable + @NotNull @ObjectiveCName("requestIntegrationTokenCommandWithGid:") public Command requestIntegrationToken(int gid) { return callback -> modules.getGroupsModule().requestIntegrationToken(gid) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsModule.java index 9d824aacab..dfa281d64c 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsModule.java @@ -9,6 +9,7 @@ import java.util.List; import im.actor.core.api.ApiGroupOutPeer; +import im.actor.core.api.ApiGroupType; import im.actor.core.api.ApiOutPeer; import im.actor.core.api.ApiPeerType; import im.actor.core.api.ApiUserOutPeer; @@ -109,7 +110,16 @@ public GroupRouterInt getRouter() { // Actions // - public Promise createGroup(final String title, final String avatarDescriptor, final int[] uids) { + public Promise createGroup(String title, String avatarDescriptor, int[] uids) { + return createGroup(title, avatarDescriptor, uids, ApiGroupType.GROUP); + } + + public Promise createChannel(String title, String avatarDescriptor) { + return createGroup(title, avatarDescriptor, new int[0], ApiGroupType.CHANNEL); + } + + private Promise createGroup(String title, String avatarDescriptor, int[] uids, + ApiGroupType groupType) { long rid = RandomUtils.nextRid(); return Promise.success(uids) .map((Function>) ints -> { @@ -124,7 +134,7 @@ public Promise createGroup(final String title, final String avatarDescr }) .flatMap(apiUserOutPeers -> api(new RequestCreateGroup(rid, title, apiUserOutPeers, - null, ApiSupportConfiguration.OPTIMIZATIONS))) + groupType, ApiSupportConfiguration.OPTIMIZATIONS))) .chain(r -> updates().applyRelatedData(r.getUsers(), r.getGroup())) .chain(r -> updates().waitForUpdate(r.getSeq())) .map(r -> r.getGroup().getId()) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java index 43bcf90ace..cb8626a19a 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java @@ -15,6 +15,7 @@ import im.actor.core.entity.Group; import im.actor.core.entity.GroupMember; +import im.actor.core.entity.GroupType; import im.actor.core.viewmodel.generics.AvatarValueModel; import im.actor.core.viewmodel.generics.BooleanValueModel; import im.actor.core.viewmodel.generics.IntValueModel; @@ -36,6 +37,9 @@ public class GroupVM extends BaseValueModel { private int groupId; @NotNull @Property("nonatomic, readonly") + private GroupType groupType; + @NotNull + @Property("nonatomic, readonly") private StringValueModel name; @NotNull @Property("nonatomic, readonly") @@ -78,6 +82,7 @@ public class GroupVM extends BaseValueModel { public GroupVM(@NotNull Group rawObj) { super(rawObj); this.groupId = rawObj.getGroupId(); + this.groupType = rawObj.getGroupType(); this.name = new StringValueModel("group." + groupId + ".title", rawObj.getTitle()); this.avatar = new AvatarValueModel("group." + groupId + ".avatar", rawObj.getAvatar()); this.isMember = new BooleanValueModel("group." + groupId + ".isMember", rawObj.isMember()); @@ -101,6 +106,17 @@ public int getId() { return groupId; } + /** + * Get Group Type + * + * @return Group Type + */ + @NotNull + @ObjectiveCName("getGroupType") + public GroupType getGroupType() { + return groupType; + } + /** * Get Name Value Model * @@ -194,6 +210,7 @@ protected void updateValues(@NotNull Group rawObj) { isChanged |= avatar.change(rawObj.getAvatar()); isChanged |= membersCount.change(rawObj.getMembersCount()); isChanged |= isMember.change(rawObj.isMember()); + isChanged |= isCanWriteMessage.change(rawObj.isCanWrite()); isChanged |= theme.change(rawObj.getTopic()); isChanged |= about.change(rawObj.getAbout()); From 82c0d714167bc571e9ea15d1cf47c6e51e7020ba Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Thu, 14 Jul 2016 13:36:28 +0300 Subject: [PATCH 022/414] fix(android): Fixing actor crashing on last message deletion --- .../im/actor/core/modules/messaging/router/RouterActor.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java index f380f9aee9..5ffc05e8a9 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java @@ -497,7 +497,11 @@ private Promise onMessageDeleted(Peer peer, List rids) { Message head = conversation(peer).getHeadValue(); - return getDialogsRouter().onMessageDeleted(peer, head.getMessageState() == MessageState.PENDING ? null : head); + if (head != null && head.getMessageState() == MessageState.PENDING) { + head = null; + } + + return getDialogsRouter().onMessageDeleted(peer, head); } private Promise onChatClear(Peer peer) { From e234cfe8904ac26ce942a53b825dc71f852e2f19 Mon Sep 17 00:00:00 2001 From: rockjam Date: Thu, 14 Jul 2016 17:36:09 +0300 Subject: [PATCH 023/414] fix(server): typing for encrypted peers --- .../actor/server/api/rpc/service/weak/WeakServiceImpl.scala | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/weak/WeakServiceImpl.scala b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/weak/WeakServiceImpl.scala index 5b00d19898..0ece36d6a8 100644 --- a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/weak/WeakServiceImpl.scala +++ b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/weak/WeakServiceImpl.scala @@ -27,6 +27,12 @@ class WeakServiceImpl(implicit actorSystem: ActorSystem) extends WeakService { override def doHandleTyping(peer: ApiOutPeer, typingType: ApiTypingType.ApiTypingType, clientData: ClientData): Future[HandlerResult[ResponseVoid]] = { authorized(clientData) { client ⇒ peer.`type` match { + case ApiPeerType.EncryptedPrivate ⇒ + + val update = UpdateTyping(ApiPeer(ApiPeerType.EncryptedPrivate, client.userId), client.userId, typingType) + val reduceKey = weakUpdatesExt.reduceKey(update.header, update.peer) + + weakUpdatesExt.broadcastUserWeakUpdate(peer.id, update, reduceKey = Some(reduceKey)) case ApiPeerType.Private ⇒ val update = UpdateTyping(ApiPeer(ApiPeerType.Private, client.userId), client.userId, typingType) val reduceKey = weakUpdatesExt.reduceKey(update.header, update.peer) From 73f91013710a3a4ab02fda9fb6ff76f3b1c9793b Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Thu, 14 Jul 2016 20:50:10 +0300 Subject: [PATCH 024/414] fix(android): Disable calls in channels --- .../conversation/toolbar/ChatToolbarFragment.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/toolbar/ChatToolbarFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/toolbar/ChatToolbarFragment.java index e93612fcf7..cf81b452ce 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/toolbar/ChatToolbarFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/toolbar/ChatToolbarFragment.java @@ -19,6 +19,7 @@ import android.widget.TextView; import android.widget.Toast; +import im.actor.core.entity.GroupType; import im.actor.core.entity.Peer; import im.actor.core.entity.PeerType; import im.actor.core.viewmodel.Command; @@ -257,8 +258,15 @@ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { if (peer.getPeerType() == PeerType.PRIVATE) { callsEnabled = !users().get(peer.getPeerId()).isBot(); } else if (peer.getPeerType() == PeerType.GROUP) { - callsEnabled = groups().get(peer.getPeerId()).getMembersCount().get() <= MAX_USERS_FOR_CALLS; - videoCallsEnabled = false; + + GroupVM groupVM = groups().get(peer.getPeerId()); + if (groupVM.getGroupType() == GroupType.GROUP) { + callsEnabled = groupVM.getMembersCount().get() <= MAX_USERS_FOR_CALLS; + videoCallsEnabled = false; + } else { + callsEnabled = false; + videoCallsEnabled = false; + } } } menu.findItem(R.id.call).setVisible(callsEnabled); From c3fa4489712abe4713c33eaea5a5e82d3de93bd3 Mon Sep 17 00:00:00 2001 From: Alashov Berkeli Date: Fri, 15 Jul 2016 02:54:17 +0500 Subject: [PATCH 025/414] ref(android): pictureActivity removed unused imports & double blank lines --- .../fragment/preview/PictureActivity.java | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/fragment/preview/PictureActivity.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/fragment/preview/PictureActivity.java index 1224218f15..f2297b5975 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/fragment/preview/PictureActivity.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/fragment/preview/PictureActivity.java @@ -6,11 +6,9 @@ import android.content.Intent; import android.graphics.Bitmap; import android.media.MediaScannerConnection; -import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Environment; -import android.provider.MediaStore; import android.support.v4.app.Fragment; import android.support.v7.app.ActionBar; import android.support.v7.widget.Toolbar; @@ -30,6 +28,8 @@ import java.io.File; import java.io.IOException; +import im.actor.core.entity.FileReference; +import im.actor.core.viewmodel.UserVM; import im.actor.sdk.ActorSDK; import im.actor.sdk.R; import im.actor.sdk.controllers.Intents; @@ -39,10 +39,8 @@ import im.actor.sdk.util.Screen; import im.actor.sdk.util.images.common.ImageLoadException; import im.actor.sdk.util.images.ops.ImageLoading; -import im.actor.sdk.view.avatar.AvatarView; import im.actor.sdk.view.MaterialInterpolator; -import im.actor.core.entity.FileReference; -import im.actor.core.viewmodel.UserVM; +import im.actor.sdk.view.avatar.AvatarView; import uk.co.senab.photoview.DefaultOnDoubleTapListener; import uk.co.senab.photoview.PhotoViewAttacher; @@ -477,7 +475,7 @@ public boolean onOptionsItemSelected(MenuItem item) { File externalFile = Environment.getExternalStorageDirectory(); if (externalFile == null) { Toast.makeText(getActivity(), R.string.toast_no_sdcard, Toast.LENGTH_LONG).show(); - }else{ + } else { boolean isGif = path.endsWith(".gif"); String externalPath = externalFile.getAbsolutePath(); String exportPathBase = externalPath + "/" + ActorSDK.sharedActor().getAppName() + "/" + ActorSDK.sharedActor().getAppName() + " images" + "/"; @@ -486,7 +484,7 @@ public boolean onOptionsItemSelected(MenuItem item) { String exportPath = exportPathBase + (fileName != null ? fileName : "exported") + "_" + Randoms.randomId() + (isGif ? ".gif" : ".jpg"); Files.copy(new File(this.path), new File(exportPath)); MediaScannerConnection.scanFile(getActivity(), new String[]{exportPath}, new String[]{"image/" + (isGif ? "gif" : "jpeg")}, null); - Toast.makeText(getActivity(), getString(R.string.file_saved)+ " " + exportPath, Toast.LENGTH_LONG).show(); + Toast.makeText(getActivity(), getString(R.string.file_saved) + " " + exportPath, Toast.LENGTH_LONG).show(); item.setEnabled(false); item.setTitle(R.string.menu_saved); } catch (IOException e) { @@ -531,7 +529,6 @@ private void syncUiState() { ownerContainer.clearAnimation(); if (uiIsHidden) { - toolbar.animate() .setInterpolator(new MaterialInterpolator()) .y(-toolbar.getHeight()) @@ -608,7 +605,6 @@ public static Fragment getInstance(long fileId, int senderId) { return fragment; } - public static Fragment getInstance(FileReference ref, int senderId) { Bundle bundle = new Bundle(); bundle.putLong(ARG_FILE_ID, ref.getFileId()); From 3cb2b0c0c832e72c2179c3367c20779990d8e03d Mon Sep 17 00:00:00 2001 From: Alashov Berkeli Date: Fri, 15 Jul 2016 03:06:47 +0500 Subject: [PATCH 026/414] fix(android): PictureActivity savePhoto check storage permission before saving --- .../fragment/preview/PictureActivity.java | 69 +++++++++++++------ 1 file changed, 49 insertions(+), 20 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/fragment/preview/PictureActivity.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/fragment/preview/PictureActivity.java index f2297b5975..b94cd6a74a 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/fragment/preview/PictureActivity.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/fragment/preview/PictureActivity.java @@ -1,15 +1,20 @@ package im.actor.sdk.controllers.fragment.preview; +import android.Manifest; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.app.Activity; import android.content.Intent; +import android.content.pm.PackageManager; import android.graphics.Bitmap; import android.media.MediaScannerConnection; import android.os.Build; import android.os.Bundle; import android.os.Environment; +import android.support.annotation.NonNull; +import android.support.v13.app.ActivityCompat; import android.support.v4.app.Fragment; +import android.support.v4.content.ContextCompat; import android.support.v7.app.ActionBar; import android.support.v7.widget.Toolbar; import android.view.LayoutInflater; @@ -30,6 +35,7 @@ import im.actor.core.entity.FileReference; import im.actor.core.viewmodel.UserVM; +import im.actor.runtime.Log; import im.actor.sdk.ActorSDK; import im.actor.sdk.R; import im.actor.sdk.controllers.Intents; @@ -48,6 +54,7 @@ public class PictureActivity extends BaseActivity { + private static final int PERMISSION_REQ_MEDIA = 0; private static final String ARG_FILE_SIZE = "ARG_FILE_SIZE"; private static final String ARG_FILE_ACCESS_HASH = "ARG_FILE_ACCESS"; @@ -248,6 +255,7 @@ public static class PictureFragment extends Fragment { private String fileName; private CircularView circularView; private View backgroundView; + private MenuItem saveMenuItem; public PictureFragment() { } @@ -461,6 +469,7 @@ public void onDestroyView() { @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { inflater.inflate(R.menu.media_picture, menu); + saveMenuItem = menu.findItem(R.id.save); } @Override @@ -472,30 +481,50 @@ public boolean onOptionsItemSelected(MenuItem item) { .putExtra(Intent.EXTRA_STREAM,Uri.parse(path)));*/ return true; } else if (item.getItemId() == R.id.save) { - File externalFile = Environment.getExternalStorageDirectory(); - if (externalFile == null) { - Toast.makeText(getActivity(), R.string.toast_no_sdcard, Toast.LENGTH_LONG).show(); - } else { - boolean isGif = path.endsWith(".gif"); - String externalPath = externalFile.getAbsolutePath(); - String exportPathBase = externalPath + "/" + ActorSDK.sharedActor().getAppName() + "/" + ActorSDK.sharedActor().getAppName() + " images" + "/"; - new File(exportPathBase).mkdirs(); - try { - String exportPath = exportPathBase + (fileName != null ? fileName : "exported") + "_" + Randoms.randomId() + (isGif ? ".gif" : ".jpg"); - Files.copy(new File(this.path), new File(exportPath)); - MediaScannerConnection.scanFile(getActivity(), new String[]{exportPath}, new String[]{"image/" + (isGif ? "gif" : "jpeg")}, null); - Toast.makeText(getActivity(), getString(R.string.file_saved) + " " + exportPath, Toast.LENGTH_LONG).show(); - item.setEnabled(false); - item.setTitle(R.string.menu_saved); - } catch (IOException e) { - e.printStackTrace(); - } + savePicture(); + return true; + } + return super.onOptionsItemSelected(item); + } + + private void savePicture() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if (ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { + ActivityCompat.requestPermissions(getActivity(), new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_REQ_MEDIA); + Log.d("Permissions", "savePhoto - no permission :c"); + return; } + } + File externalFile = Environment.getExternalStorageDirectory(); + if (externalFile == null) { + Toast.makeText(getActivity(), R.string.toast_no_sdcard, Toast.LENGTH_LONG).show(); + } else { + boolean isGif = path.endsWith(".gif"); + String externalPath = externalFile.getAbsolutePath(); + String exportPathBase = externalPath + "/" + ActorSDK.sharedActor().getAppName() + "/" + ActorSDK.sharedActor().getAppName() + " images" + "/"; + new File(exportPathBase).mkdirs(); + try { + String exportPath = exportPathBase + (fileName != null ? fileName : "exported") + "_" + Randoms.randomId() + (isGif ? ".gif" : ".jpg"); + Files.copy(new File(this.path), new File(exportPath)); + MediaScannerConnection.scanFile(getActivity(), new String[]{exportPath}, new String[]{"image/" + (isGif ? "gif" : "jpeg")}, null); + Toast.makeText(getActivity(), getString(R.string.file_saved) + " " + exportPath, Toast.LENGTH_LONG).show(); + saveMenuItem.setEnabled(false); + saveMenuItem.setTitle(R.string.menu_saved); + } catch (IOException e) { + e.printStackTrace(); + } + } + } - return true; + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + if (requestCode == PERMISSION_REQ_MEDIA) { + if (grantResults.length > 0 + && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + savePicture(); + } } - return super.onOptionsItemSelected(item); } private void showSystemUi() { From 8f2c6ebbe4da433641a5a4c6231381794a5f401a Mon Sep 17 00:00:00 2001 From: Alashov Berkeli Date: Fri, 15 Jul 2016 05:54:29 +0500 Subject: [PATCH 027/414] fix(android): Check location permission in MapPicker --- .../java/im/actor/map/MapPickerActivity.java | 40 +++++++++++++++---- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-google-maps/src/main/java/im/actor/map/MapPickerActivity.java b/actor-sdk/sdk-core-android/android-google-maps/src/main/java/im/actor/map/MapPickerActivity.java index f581ca9b2a..0772c7522b 100644 --- a/actor-sdk/sdk-core-android/android-google-maps/src/main/java/im/actor/map/MapPickerActivity.java +++ b/actor-sdk/sdk-core-android/android-google-maps/src/main/java/im/actor/map/MapPickerActivity.java @@ -8,7 +8,11 @@ import android.content.pm.PackageManager; import android.location.Location; import android.location.LocationManager; +import android.os.Build; import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v13.app.ActivityCompat; +import android.support.v4.content.ContextCompat; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.SearchView; import android.util.Log; @@ -50,6 +54,8 @@ public class MapPickerActivity extends AppCompatActivity GoogleMap.OnMapLongClickListener, GoogleMap.OnMarkerClickListener, AbsListView.OnScrollListener { + private static final int PERMISSION_REQ_LOCATION = 0; + private static final String LOG_TAG = "MapPickerActivity"; private GoogleMap mMap; // Might be null if Google Play services APK is not available. private Location currentLocation; @@ -341,6 +347,14 @@ public boolean onClose() { * This should only be called once and when we are sure that {@link #mMap} is not null. */ private void setUpMap() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { + ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, PERMISSION_REQ_LOCATION); + im.actor.runtime.Log.d("Permissions", "MapPickerActivity.setUpMap - no permission :c"); + return; + } + } + LocationManager locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE); for (String provider : locationManager.getAllProviders()) { currentLocation = locationManager.getLastKnownLocation(provider); @@ -362,6 +376,20 @@ private void setUpMap() { mMap.setOnMarkerClickListener(this); } + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + if (requestCode == PERMISSION_REQ_LOCATION) { + if (grantResults.length > 0) { + if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { + setUpMap(); + } else { + //FIXME: if user checks "Don't ask again" button, in next open of activity, it will be finished without any notice about permissions. Need to show toast or alert dialog (with button which redirects to app info (for granting permission manually)). + finish(); + } + } + } + } + private void fetchPlaces(String query) { mMap.clear(); @@ -418,10 +446,10 @@ private void showItemsOnTheMap(ArrayList array) { markers.put(mapItem.id, mMap.addMarker(new MarkerOptions() - .position(mapItem.getLatLng()) - // .title(mapItem.name) - .draggable(false) - .icon(BitmapDescriptorFactory.fromResource(R.drawable.picker_map_marker)) + .position(mapItem.getLatLng()) + // .title(mapItem.name) + .draggable(false) + .icon(BitmapDescriptorFactory.fromResource(R.drawable.picker_map_marker)) )); } } @@ -436,7 +464,6 @@ public void onMyLocationChange(Location location) { mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(location.getLatitude(), location.getLongitude()), 14)); } this.currentLocation = location; - ; accuranceView.setText(getString(R.string.picker_map_pick_my_accuracy, (int) currentLocation.getAccuracy())); Log.d("Location changed", location.toString()); } @@ -445,7 +472,6 @@ public void onMyLocationChange(Location location) { public void onItemClick(AdapterView adapterView, View view, int position, long l) { MapItem mapItem = (MapItem) adapterView.getItemAtPosition(position); - Intent returnIntent = new Intent(); returnIntent.putExtra("latitude", mapItem.getLatLng().latitude); returnIntent.putExtra("longitude", mapItem.getLatLng().longitude); @@ -481,8 +507,6 @@ public void onMapLongClick(LatLng latLng) { //currentPick.setPosition(geoData); } - - } @Override From 7737ad5fc93605af35e116a32d08aae6f952e1bf Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Fri, 15 Jul 2016 08:50:42 +0300 Subject: [PATCH 028/414] feat(core): Passing isChannel and isBot arguments to dialog list --- .../java/im/actor/core/entity/Dialog.java | 41 ++++++++++++++----- .../im/actor/core/entity/DialogBuilder.java | 18 +++++++- .../messaging/dialogs/DialogsActor.java | 29 +++++++++---- 3 files changed, 68 insertions(+), 20 deletions(-) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Dialog.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Dialog.java index 8e98424e6a..e2ecc7a94c 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Dialog.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Dialog.java @@ -50,6 +50,13 @@ public Dialog createInstance() { @SuppressWarnings("NullableProblems") @Property("readonly, nonatomic") private String dialogTitle; + @Nullable + @Property("readonly, nonatomic") + private Avatar dialogAvatar; + @Property("readonly, nonatomic") + private boolean isBot; + @Property("readonly, nonatomic") + private boolean isChannel; @Property("readonly, nonatomic") private int unreadCount; @@ -82,14 +89,12 @@ public Dialog createInstance() { private int relatedUid; - @Nullable - @Property("readonly, nonatomic") - private Avatar dialogAvatar; - public Dialog(@NotNull Peer peer, long sortKey, @NotNull String dialogTitle, @Nullable Avatar dialogAvatar, + boolean isBot, + boolean isChannel, int unreadCount, long rid, @NotNull ContentType messageType, @@ -104,6 +109,8 @@ public Dialog(@NotNull Peer peer, this.peer = peer; this.dialogTitle = StringUtil.ellipsize(dialogTitle, MAX_LENGTH); this.dialogAvatar = dialogAvatar; + this.isBot = isBot; + this.isChannel = isChannel; this.unreadCount = unreadCount; this.rid = rid; this.sortDate = sortKey; @@ -130,6 +137,19 @@ public String getDialogTitle() { return dialogTitle; } + @Nullable + public Avatar getDialogAvatar() { + return dialogAvatar; + } + + public boolean isBot() { + return isBot; + } + + public boolean isChannel() { + return isChannel; + } + public int getUnreadCount() { return unreadCount; } @@ -164,10 +184,6 @@ public int getRelatedUid() { return relatedUid; } - @Nullable - public Avatar getDialogAvatar() { - return dialogAvatar; - } @Nullable public Long getKnownReadDate() { @@ -188,8 +204,9 @@ public boolean isReceived() { } public Dialog editPeerInfo(String title, Avatar dialogAvatar) { - return new Dialog(peer, sortDate, StringUtil.ellipsize(title, MAX_LENGTH), dialogAvatar, unreadCount, rid, messageType, text, senderId, - date, relatedUid, knownReadDate, knownReceiveDate); + return new Dialog(peer, sortDate, StringUtil.ellipsize(title, MAX_LENGTH), dialogAvatar, + isBot, isChannel, unreadCount, rid, messageType, text, senderId, date, relatedUid, + knownReadDate, knownReceiveDate); } @Override @@ -201,6 +218,8 @@ public void parse(BserValues values) throws IOException { if (av != null) { dialogAvatar = new Avatar(av); } + isBot = values.getBool(15, false); + isChannel = values.getBool(16, false); unreadCount = values.getInt(4); sortDate = values.getLong(5); @@ -224,6 +243,8 @@ public void serialize(BserWriter writer) throws IOException { if (dialogAvatar != null) { writer.writeObject(3, dialogAvatar); } + writer.writeBool(15, isBot); + writer.writeBool(16, isChannel); writer.writeInt(4, unreadCount); writer.writeLong(5, sortDate); writer.writeLong(6, rid); diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/DialogBuilder.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/DialogBuilder.java index cfa9fce7de..8d1fe1f61a 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/DialogBuilder.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/DialogBuilder.java @@ -22,6 +22,8 @@ public class DialogBuilder { private int relatedUid = 0; private Long knownReadDate; private Long knownReceiveDate; + private boolean isBot; + private boolean isChannel; public DialogBuilder() { @@ -41,6 +43,8 @@ public DialogBuilder(Dialog dialog) { relatedUid = dialog.getRelatedUid(); knownReadDate = dialog.getKnownReadDate(); knownReceiveDate = dialog.getKnownReceiveDate(); + isBot = dialog.isBot(); + isChannel = dialog.isChannel(); } public DialogBuilder setPeer(Peer peer) { @@ -98,6 +102,16 @@ public DialogBuilder setDialogAvatar(Avatar avatar) { return this; } + public DialogBuilder setIsBot(boolean isBot) { + this.isBot = isBot; + return this; + } + + public DialogBuilder setIsChannel(boolean isChannel) { + this.isChannel = isChannel; + return this; + } + public DialogBuilder updateKnownReadDate(Long knownReadDate) { if (knownReadDate != null && (this.knownReadDate == null || this.knownReadDate < knownReadDate)) { this.knownReadDate = knownReadDate; @@ -113,7 +127,7 @@ public DialogBuilder updateKnownReceiveDate(Long knownReceiveDate) { } public Dialog createDialog() { - return new Dialog(peer, sortKey, dialogTitle, dialogAvatar, unreadCount, rid, messageType, - text, senderId, time, relatedUid, knownReadDate, knownReceiveDate); + return new Dialog(peer, sortKey, dialogTitle, dialogAvatar, isBot, isChannel, unreadCount, + rid, messageType, text, senderId, time, relatedUid, knownReadDate, knownReceiveDate); } } \ No newline at end of file diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/dialogs/DialogsActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/dialogs/DialogsActor.java index bde055b7ac..6f6943076f 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/dialogs/DialogsActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/dialogs/DialogsActor.java @@ -13,6 +13,7 @@ import im.actor.core.entity.Dialog; import im.actor.core.entity.DialogBuilder; import im.actor.core.entity.Group; +import im.actor.core.entity.GroupType; import im.actor.core.entity.Message; import im.actor.core.entity.Peer; import im.actor.core.entity.User; @@ -86,7 +87,11 @@ private Promise onMessage(Peer peer, Message message, boolean forceWrite, .setMessageType(contentDescription.getContentType()) .setText(contentDescription.getText()) .setRelatedUid(contentDescription.getRelatedUser()) - .setSenderId(message.getSenderId()); + .setSenderId(message.getSenderId()) + .setDialogTitle(peerDesc.getTitle()) + .setDialogAvatar(peerDesc.getAvatar()) + .setIsBot(peerDesc.isBot()) + .setIsChannel(peerDesc.isChannel()); if (counter >= 0) { builder.setUnreadCount(counter); @@ -102,8 +107,6 @@ private Promise onMessage(Peer peer, Message message, boolean forceWrite, } builder.setPeer(dialog.getPeer()) - .setDialogTitle(dialog.getDialogTitle()) - .setDialogAvatar(dialog.getDialogAvatar()) .setSortKey(dialog.getSortDate()) .updateKnownReceiveDate(dialog.getKnownReceiveDate()) .updateKnownReadDate(dialog.getKnownReadDate()); @@ -121,8 +124,6 @@ private Promise onMessage(Peer peer, Message message, boolean forceWrite, } builder.setPeer(peer) - .setDialogTitle(peerDesc.getTitle()) - .setDialogAvatar(peerDesc.getAvatar()) .setSortKey(message.getSortDate()); forceUpdate = true; @@ -342,10 +343,10 @@ private PeerDesc buildPeerDesc(Peer peer) { switch (peer.getPeerType()) { case PRIVATE: User u = getUser(peer.getPeerId()); - return new PeerDesc(u.getName(), u.getAvatar()); + return new PeerDesc(u.getName(), u.getAvatar(), u.isBot(), false); case GROUP: Group g = getGroup(peer.getPeerId()); - return new PeerDesc(g.getTitle(), g.getAvatar()); + return new PeerDesc(g.getTitle(), g.getAvatar(), false, g.getGroupType() == GroupType.CHANNEL); default: return null; } @@ -355,10 +356,14 @@ private class PeerDesc { private String title; private Avatar avatar; + private boolean isBot; + private boolean isChannel; - private PeerDesc(String title, Avatar avatar) { + private PeerDesc(String title, Avatar avatar, boolean isBot, boolean isChannel) { this.title = title; this.avatar = avatar; + this.isBot = isBot; + this.isChannel = isChannel; } public String getTitle() { @@ -368,6 +373,14 @@ public String getTitle() { public Avatar getAvatar() { return avatar; } + + public boolean isBot() { + return isBot; + } + + public boolean isChannel() { + return isChannel; + } } // Messages From e40060635ddbe2d46434665d320dbbf387bcfc50 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Fri, 15 Jul 2016 08:51:20 +0300 Subject: [PATCH 029/414] ref(android): Delete xxxhdpi icons --- .../ic_action_editor_format_quote.png | Bin 1295 -> 0 bytes .../drawable-xxxhdpi/ic_content_clear_gray.png | Bin 1174 -> 0 bytes .../ic_editor_format_quote_36dp.png | Bin 1280 -> 0 bytes .../ic_editor_format_quote_gray.png | Bin 711 -> 0 bytes .../res/drawable-xxxhdpi/ic_email_white_36dp.png | Bin 812 -> 0 bytes .../drawable-xxxhdpi/ic_favorite_white_36dp.png | Bin 1188 -> 0 bytes .../res/drawable-xxxhdpi/ic_home_page_18dp.png | Bin 1860 -> 0 bytes .../ic_info_outline_black_24dp.png | Bin 1568 -> 0 bytes .../ic_keyboard_arrow_right_white_36dp.png | Bin 286 -> 0 bytes .../drawable-xxxhdpi/ic_more_vert_black_18dp.png | Bin 294 -> 0 bytes .../drawable-xxxhdpi/ic_more_vert_black_24dp.png | Bin 389 -> 0 bytes .../res/drawable-xxxhdpi/ic_pause_white_24dp.png | Bin 94 -> 0 bytes .../ic_play_arrow_white_24dp.png | Bin 343 -> 0 bytes .../res/drawable-xxxhdpi/ic_share_black_24dp.png | Bin 1156 -> 0 bytes .../drawable-xxxhdpi/ic_social_public_24dp.png | Bin 2671 -> 0 bytes .../res/drawable-xxxhdpi/ic_star_white_36dp.png | Bin 1306 -> 0 bytes .../res/drawable-xxxhdpi/ic_twitterlogo_18dp.png | Bin 1407 -> 0 bytes 17 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xxxhdpi/ic_action_editor_format_quote.png delete mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xxxhdpi/ic_content_clear_gray.png delete mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xxxhdpi/ic_editor_format_quote_36dp.png delete mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xxxhdpi/ic_editor_format_quote_gray.png delete mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xxxhdpi/ic_email_white_36dp.png delete mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xxxhdpi/ic_favorite_white_36dp.png delete mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xxxhdpi/ic_home_page_18dp.png delete mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xxxhdpi/ic_info_outline_black_24dp.png delete mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xxxhdpi/ic_keyboard_arrow_right_white_36dp.png delete mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xxxhdpi/ic_more_vert_black_18dp.png delete mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xxxhdpi/ic_more_vert_black_24dp.png delete mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xxxhdpi/ic_pause_white_24dp.png delete mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xxxhdpi/ic_play_arrow_white_24dp.png delete mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xxxhdpi/ic_share_black_24dp.png delete mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xxxhdpi/ic_social_public_24dp.png delete mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xxxhdpi/ic_star_white_36dp.png delete mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xxxhdpi/ic_twitterlogo_18dp.png diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xxxhdpi/ic_action_editor_format_quote.png b/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xxxhdpi/ic_action_editor_format_quote.png deleted file mode 100644 index b0e32e834feb3a1754227254e2ec8f7918088707..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1295 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7xzrV7cb$;uum9_jXQncW~Oj`rSK_}qGimEfV>)pr_4JRO*zRMe}L6=$rTk&$^W6n zhBvCK@7;Pm^Xrznd|O}R%~9?Df1RzG_xIbr%kK-9hAo)CtE%fgqeBq~(Ar7b6ZX`o zF)*lvD^&jUVPNoB=kV#%Oa_JtF^x~28Z$B|>|{E5I-QBZp@?wE^w37&dR-Jng*HT35f?pQ`(f|JL2#_peyDq-Oan_g%eg2dq-7L$kXl zOU!@reG$V9Ii>?qfB&>yOI!Y9{?qcLh??ayd>J=5-n6@L>y^qe_Mf)v*!D;>H8B3T zV7QO5A_pixi!osy6T=5_zKh!!)Eu4~GBA8*xvn@tcY#queM>O&42G%F2UrrALfrCP zEYuwS#u+fqVEDRHT5KX`zoRyXd*7&tULrXnl}Z;B$`QYxO~(rYp<) zj2Y4!8hJLbUiqJ#!kxgq!e2I;0SFx??6=;=u&qHsLyMQM5vDrth+@Isy7J)FUp9VF z-4wfY>y8av)iQsMbS!UryYa(q*+;j3bAOX_&aA1{`my5Ce*H7o@9NxQF>rjl(Ds45Kh~#~M}6Ecc5CF{n|7rBj_Ns$=%)#nA33nsKNtBQ$zEN4c^cylwNkmvn_>Y)d#7F$ zG=1RR`SE|wl|QkYHcpT5TCaK3E;1|T^tx1rb>2&wWm`6dZ<<%OK|X8Mv-KDB->%uT z{la|5a>tL$x!$gxY0B9kwIP`6?)9js6<4%x&JAL&6T8)K68z_{(T+u1cU`z&d^1++ z`WMZf$dqd`3_|z$I?A_x6Aiub`tO9hlAZrIUHDgjcKhbzE!P%0)t7zSvG9jK(9~I5 zIU0KJW=DPdxcZ4yv)?EF$XxeoH1ljDE=P(Q)0#*_jSN574x+By$r*n?Cn;?bDC~srFjd()YMvp zx4eW$RwM6SbS*P4OihzxFFVb2Hp}vcuGAdIlti8Tw$J2 z&z~dOaQMGqEjMW45iJE%&cx?>Aj;5 zw~@`h@NWDl;7iC_v4cbYUol5Wg6)FCg2O{^o0UqphclH+ZTnVt(=)BoYH0MJ?_ZKS z=s@R82bN4RG&$uC{a>&9$`?~*vXm5)jK2L`+0$=*Cl@TeI$lao3aWlb+q75e4FVSV zk{h*L{ya9(%l_(l(WrE0HSnZFj{h}JusZT=rTo=O*BiR{vl5T|k7v^ZH_Okg#e17s zjOLofv3;uyR`%WD@1!m@R|k6Qj#MrcT`UVGazHSWgs!+q7$Eg7^A!I#%n^vh^UUXWLN@O8!XE!h09V z^cb1=Adcf>6WXUWyM4gWpV#{8ujJ@d+EadsNBdnIK`dc}_Qc)L5+*iaForZ%H&8o6 zk4<7QKw?^Ba}m8Y(-nSx$t}T?^2>`8-(=_VI}M};3|z4#wLzh* zxKjW|V-zw}i!lW9*TYWU$L!<-s-R0o2k;Z0%|mh^B@Nd2ka0{7F|0umkl<%4|AYG5 zr>l92j));sXq9q!2@187%xy70{rkb(ag%e1qU}+S)h3D~92%Lhu5a(c&lI_$VTy#G z9M`Hua#ZMY+ODL214a9J2Np02!K_&VI0*b6@xXyI74toVZ?}3SpbiSH&k{dmM!xE+ zjgyq9+pY8B)0=q9RIRf*$9SEfE(9gp`ZR6d(PKKh1x|y z>NjT56y_8tM?50<1Pg8A+Cw|3uIbi`dlvnEtj(anm|sXisfnYE`tdL2nN9rEm}qVv zMDZni!bC7~h|9K*`EktbS9jCVeo)A`-Ls0-Gg4}2Ef(7E2j^CMp z;~8=LYwL%Hn+&GDueXYYy^4pbO?yoX52$Z6={sFTuJ^9qr$%s*O3m4Ja+W{p+@zD= z&v){(UdHSx53D+rG|HTC!f6+q13LG8G}aAfmNn}_2=-4*OCm&H0Oqu#ktqw9R<6(| zaxbvGkI|6;B=iX(0mTti6kaZ`R?+Z)>z>hQ$z%rei!J3huQA1F3WZh`fYu5RsOupD zq4SpbiVi4KC3qZTTV@OUf?xr|8b`UMN62OgBsZq z=E{1pRy8&{aG1C=G@9fEZ4vO{S2?g-#*06suOz`CFOhk}i^hMgtsn&ravqP>T)7W^ zabU8kQkds5{q^DIBfJy%cs%({7`oMFsa5$+=f1OwO-i<1E@R4#vyUtJN`x=Au@`X9 zZa%%+VB5#yXLELVUUV|pZonvayJFkNr@uDz-e-7uV8Ry%<_$NFZSI|axi$AsXkN$y z%cVD3H*(K5ZjZfj|IP}BV!6OZAo(l+NKUfkXmQ{Oacqd5;WAN#@AAR6*z=fyH^Kbtw7nA?z zDZb)#Fyp(8x(-L3_;eG5Uqo}S5cuwG{lAMl^Ti+k?Hvynh%&9PTfaJCwcSgmuU!}O z8DBkL7`ylUD}}`$-!gTqo4341IX^LEet4|z-rGNqPSOhaa&>q6J@su&fwk9O)J|gc zdSCmfJZ|H5gH`9FuBzvWUt{rFZ+CI;?WJ?me1v_!My~(m61w2abG7PQl}EoiSg)|- z=dC)rn(cM)^Bd7O_*VIczE7@xUi#Ma_b1y6KyH2Ht?ln#rSE(te3AKW!t>Cr#an8U zA58n|9lqeutK^+kw|~Ch^-Cwdk!yAT(^tB0?oNAMJFR|dU_`A>{u=J{_k(Qn@9q*} zS#@h?K<&TlZJm#&y=mlHAGhj!i-@w~YOav2#f7!EH@y0zd(}8fVb%Fr@Ao9hsqEWh z{Hp)o`QKaqRd4)m_k6#qwe0nBy}d#;FP=aB^SgYj|JA>@^6%a|z5aMz>EBC9EH@@? z-!PHI;+i)@wrsW4%B{hv1+ zBKuqJu?UVCwU9aSW-5dpj%9?~sE?%Sv(Q zy$v1@COHWBE5x1oHR}e8=bdj8bM1xg{w18c6`59+c5BnwL*l0#nip~J3t*@a)M#Kj zz;ob(l>=h}TSA5G0tN$SgFSo!3^N!d;@CA9&M@$-XBJ^dV`#h1$i-mHaQHO?YlCFN z!&2PZ_U&T6we4@|xn-G;0;KbOx$CdjbH>Lu{H?Rsym2LxlhdM0Ed>oq_Rf3rn1KI>4Hx}FC6GHY$}>&)KpX>>F72=@X6eQ zOx_{D}6L{$F zwh48Uu1nng_%7oAyj70Y3a-~5yg%M=_(-|%R58bgDH8EAyKcXow(0Q4ullalw$?drefoThKmIGszvMTX zVfS}kTW2CAFRl<)FK#PoZQsf;bB9Kk*W<-ZN2c%p{pL8!g3Z&9?cetFjjTg@{*L#m z9u?BJZ4KYqcKzQP_Wc3Vg`3_$r5A42GuM56UFXAKRh{`~zuw8T*9=y_H~$Du|Nkc3 zvE7??mAJYD@< J);T3K0RU2dFZ}=j diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xxxhdpi/ic_email_white_36dp.png b/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xxxhdpi/ic_email_white_36dp.png deleted file mode 100644 index 309263b51075a150e4989e5d8f5930e2ee95f5c8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 812 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q1xWh(YZ)^zFn#uPaSW+oe0$f{FChPI7x*q*_#%WKHxW=?s|FVerw6Q&4H zuQiS@v1ubz{m!KDV8J9X4hhUwo%- zfyb8Az&sw4Wj_zWtWY==wcr_o!=#Y8rFhC}CN39ixm3Xml1)!DxEJX#TynE^zv*Ck z>#2rxhs@ieDZEn_&t06e;L|NelZ9)ICp7Y;C@@QActxiJsS^%tMi(q|=RyQeIEbDB z3QTDH0#kOvAvx=o1)s@+WxS6Z+W(aOOW)4FGV`xRK)>eJ94~#oDHWc&w^h$FmGlal z=A0-J(YViMnzQ%hVddK;wYPoeuiJTc!n(W@zg%tVww(FwXIHmn_HAJnEtcKqrTZ+G zzLhq9&|tUs!{`npK(66QS#XZ&XC1@iX0AS4&-W{Uxq!jb)z4*} HQ$iB})n9Z! diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xxxhdpi/ic_favorite_white_36dp.png b/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xxxhdpi/ic_favorite_white_36dp.png deleted file mode 100644 index 8ae2588db12d926917d538b9a37218bdea901902..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1188 zcmV;V1Y7%wP)_5$9$)ZPNtB%pxQsu`+}Rw48}K=%Ow{q8m-Ci>}Jjgl>x! zX&RL8s8nK_OGSp(G}1~vZlcaX?$zylOGps$?Rh=V^Zfsxk7QYvWm%SGS(as4mStI% z6*;m@pq7^I$m}G<8;s_>bkXtlklDyoa6O**dEee>VS8maTNqXcK=P=17xy5lz z(k!<)gh?9Y7F#jNR=LFom}HgQVj(7ZO>Qw6lRP4~=tmc(=%%0CV<)EAB`6kyk{39fzyyEb$Qzbog5~mxVf2!A1xCs{Hjs9kS)|zu z@(YIpq}4$(@{gIM)eHrL??|JK3JMi;kTzY6R&ZEG+PtG6QNl6OcrM8(5E z4iabk8K~l72*-%4Mh2_+7|x%>(Q$?;9;o0XadVP##RsFv6DN5_DP9;$GjVZ-O2rS= zoFxvL7^`^VL7FJGGmKGuQAwU+I!&eGjZvJWSpMb##UB;?MKPRUgpz^b93yIt3{!G& zKMh3b5cerrxQBY8vX{G+Je2YyQP{}tFA3A<^ii^rVKre{Nk++s z!+V5b8IIC|HwfD6N*7+HhoD^MC8ZC~(oPWCn5lH)F)rd)3saR|j3bX9r>Ii8QO@sp z)kvArk0Bhui+v2187O53Zf&K%%t2q);Kmo^WEL{4;!b@?M&`j`33uWx9GQw*dbyPz z7RqeQrh}VlXO=7;rvMkH8EX%Si%d#vhJpTfR!X4&|$i~qC0000YFKOQ$dK}#T4@tRqY!kJQyBKF@~60 z6(OdG;i2Z3ic&)@`Pcfs-0$AA&))kC-@R$>e!IDQ{GGMeS$nOu4>2sI1UTeVPGATq zA|XRSLqPSE$WVd?O@hp9GJpdC>CTBHum|h0q`S$ZvcD-;5`6u0eFq% zs(Lb!*Lqwe$jqhyI2FJN0QLtkA#a+awf_OI48UUm9wNE2Vn4~vS_NcghXYsu;Aj9F zCeJFZ;#vSt1GtUkb7@WTYqbK%%nk!^txHS(tnyn(sxBk>LVnY6w1doSCjfT|W;Pvwl=AK+Qljm4B*`qDR0WWfUq2P{0+P3PgD|s60PY8HTnVJbB+t&- zYFU8%RlYQ5fQIX8tIP;Vftkr#Uj*Qcl5ywcm;lLu+z5QXk9LmW91}AF%}i^$r$owS zC+!vMX9RE#pojZlA^!nD4@FYDC{=&BB+2|ZJ|IbtkUTj>x|0C;+3NX}4y)85nKX}_ zzFjB131E*vyO}9xx1@=g*&A&hJlg=|W`KN7uLGD^0BIS?17gslfZSyvgRr&I79fXo z9e_&%la~Qm>MBVA^f-WHTcZP@Wk8z)_!z(r1#5VMWSLojQb6tr{JaHc*OrgR=p7^x^xqx0ifLB8PF~4F97UV(0&$41psQQPQ;46aH`vh9Bl5?PR)-|)x+e|UG({ceV2QbF(;vK9LM>7ja z*qCt!Oo9}SDmpSPC#isD))v!iap!_Idy?*0AxRO zm&u(PKL%;Q0LexBOTn5}la$4sB|(aV7ln%_lGJ~rr3})50onk7tmp2smSmw%yX-9% z0yKeS&mb{Ls;XE!+SN7-+)p&-;d=j-0EM>LB$B@tEqELtY1qn!rAI*;+hQRlJ&mMt zb_&ZlK&pH+F|%m^K50rxhhFnhPh8uOs_Hc}n+c%E|5NETA2Jk`J)u~9X=xKu4L~~c z^@NN(87tdR4B0qKNzRP{(*JEissTvOe`TR`mr6&C&|y`%H&aMjzaUis98svO~~CbourZ~lSwWyfBH5S85VcML^O$MSiDh z4l@T7a=HHC+f0GIxF%Mn<>{h6ga$wq|AY&VrJTs zBiZyxZ4xReEB^yeeOX9SzekIi-5|Gapr87ILx&U!J^$tDDDNaJM0Sy!=1O*ItLVyt zGCAfQ3CfVB>yd2iKp-BjMsMJ=LGij+lPmO!Qbe_rfSdm@&? zNo_*9vzU<&boaDNDh-8-QN_DUx)KhhTIis%gbt_MF;D!Ry#+u_R7iRhmmO zs*^hUY{YhSA1JrB)3;UR8J~EZTx=`0ie0U`@L9Sha4=?qmuo_+qPoea`+oAKr z0?KZ$dWT*)0dl>pA0HM7*gU!wtxr?PFQjwWR1d`zeVXEHclpZCvqIMj*2_;*BnsL< yR%`4vKai_yU{XUs5r{)TLqPTR*-(Na{Qd)C)1qgPE`vD$0000; diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xxxhdpi/ic_info_outline_black_24dp.png b/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xxxhdpi/ic_info_outline_black_24dp.png deleted file mode 100644 index c31e312e287148020c79bd28cab420a7d34584b3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1568 zcmV+*2H*LKP)OW(f`jGM^$*y)sGg2Z-s9B#mGCF0~=G}#bqvD zsZ!zln!4UXHirD<>lmMEILDE%d(?RjvR#Ee9VgU>X8c;su=5jgc|iKFKn`h!qhRKQ zg64#KHqe3?g}`o&aSVi#9i7n#In5bJB3d;<(&-QuSrSG6gr{ZZtY9`7AGt z3yuF_1{ad+gid&Xtn;DZMPb-!g6v|8FdX&ysA@`H&t}Lz_6W!7tcpq*jHIM|){=Zq zt__?M)~uZDC+rvI`4u^GKqbibs9Rku;l~7V-{vCAhXV-j3g&~m@U*eO8d?MfIBxkN z@Z|tPhd};LTf)l(<%Tf_wH==hs0O~0dL1lvTdWZbzRkfL2q%dv~vLiwVa4Go@rUcCsyl(XoI$)8&F4Zp5&k=|hw;9{4G9hFyBcvx{2XLwR zZWBVf0$6;7iq9C^beIrwnGv!fVh3>P`8OtnTw{bRaXjb{7Ggy#V|4l6gpg52NNvOp zn9u0)s|g{KjF3Xa4k!oyJlc%$gb`8}u>;B&T^=U~9AtDkna5&WNDes9=+a_B2v?528L;^|$k_4_LuRlTuTm^?Mnwx-cOKFJ60gVDZ)|y^& zPcQAN;Eyd-!4)afdgIx{%qWrHUzI04E{g_4md2(;R73jspWc@5lnY4 z%*n9>C?$UY{3-fbwiIX<7@$4J4(Jrf-)>U@YCpdx$cR?t&;joWWRHS_YZ%%DiVT6h zvjRRGP$dYz#%eztXd`+=piFm;9MCI}djlNq4sHe6;pJAyKK2X8`jKnJ`UFbRe6Tsg z#{U>(R~20Fq1*zH-MvN^UaZTY@u}tVkzjn3dcWaIN$H&t z=%D1H4LT=wOJfl9n$t#4wMU()w$r7dGm)<9se?e#sOf}a8T*h50j@wL<->x)+KU`&<6U|8R*j= zV$7FhN1Zbmbdk4Jc0vc@s1=a3(Sjhd$J7?6J=q->`YY%B4V z_s#o|qn|UW6V{GG;RT=N1>05Bk$??9?4zpb8s(}aaW^2tkE%((01<&Mf!bMO%4&+V zdfyI_|GXh}TTmAy@0VeDybd z+pb-~)z2V(;$U%hl!WpY~ z3#F%Pu$8||zu=+oYIIp@ijv8-)ep{;^WEW%k@GBUXJlY#=m}HZJt_EPOtH>PuVZhX zuJQiowNAfC@8`)H%e6)GynmgFxUg>Voa$|o+*Z08FD7hUQ0eF>&Z_xh{^O>mZ<5@z z#6-57jJOa{|1i*Ue`Nh<=USF|%|-h*?_YNO*E!28{U4Xs6`Re}XJ@c$cHMYzkwi=% znA)Jh#*@ZX)$eMU5XciDVhu$4Cl_%r$e&zv11O?*985)sFtZu6?mfOp0;pc6OID|A j?(~#E76yg~57}-$3HWBY%C;ad8VF`6@{t*6cIFNo@6((BC&yoL0GNPYP!PSK;PCZ= z0_g<>pZR)0f%Jlc=>-K-OR<2*rWX`UFDRH^&`h7AdO;QG1vRn+y`VT$m`Z~Df*BafCZDwc^29w|978G? plNp)=Km51n;8#DVkv_$ki-GCDeZ`wzAGd%sdb;|#taD0e0szsl7@+_F diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xxxhdpi/ic_play_arrow_white_24dp.png b/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xxxhdpi/ic_play_arrow_white_24dp.png deleted file mode 100644 index be5c062b5feeba5eff766b2fdae6dccb60cb4b0e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 343 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD0wg^q?%&M7z$oYG;uuoF`1aODUuH)UwnS^i z62=e)(*V{`2k`*b5=Y_d%(4?EPx_&_WMu+pnDW{BolE^Uxj)}+yLIc`8R zg{90Lt0f&`?lL^eViO2AZ`iSm@yJ!6R31a2DNst&xD8@?udTeN#I8%r_sg2!eom$?*X_%R&Z!t~|1-cBYK4gm#+1_nk5gW={^?qALV VOCpPJ@c;vi!PC{xWt~$(69D@(h;je` diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xxxhdpi/ic_share_black_24dp.png b/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xxxhdpi/ic_share_black_24dp.png deleted file mode 100644 index be4e7d0db7af7686dae09bd84e2ce27b9a18837d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1156 zcmV-~1bh35P)&RR|l+$g^O6R2yNZKQh%2Vygh1q*<`lJC`k*A5}GM^?vt992B63ln#6K z9B^QMRQqACo<@fSsE56J-Z(5k8|>B7?XUp9V6UD(4h!%T_UdVKSb$fsS5LFU0^EeX ziCO440kL+&=KmQ_DkuGRY(9VgblBYs?__NweROPmR28tim!+f!Niv@GnKT8q%ORjD ziN;5F$kvA4_>pRjr_-l-?*cB&@dDiTu$#|!-*cbFbqTroK1yBx$-sQRW=)ulUqHHT zU>xrS#BZKVC7tn@lz1uQG$}Q##*ZVF8N|wmq-C1#=0X&ZuBpGOZ=?>=Qz5%1c7}Pf z@#N?>Ab->e}}XP_DNCKBGn?EZS`i<+=q-OzQWhlu|@y^RbO^4fmVRmib2JM828J`3w8?TD3TY)bN?7h+`$=>`j5L4;Tt$E740ANp9C zDDm_-HlAZ;Ntk2hKomPRKlHIuM}tH28(^;3Mc zWxa57-gcn918$z04C{L-2v`KZ)^Gdx7a7VNV}mNM0E!m1DjA1t=M75-b{W7R6;@;nF_TZ4$_mI<&vcx(-#8dz>Gwwh`= zEVmn5{i+F8yNRuSp1jjwvz`)%MaYA_ddeIYpcMA%sdiX^O4zHX3I!Nk$}t2DF8K}% zfd47ufeeQ+6tAuHh>N%9Ho{i#F>a zG=Bzc3?EqW=0000l(g*7Dl_0q_q1e+O_EfIk7a)&G7D;C7N$nb$MBAC&}{+0y{L0l>}xb_MV<08j47 zxPJrq6@c#o_&$K|kbJ0Po5iYE2f)mp3Sf5t?{TI-quA_bH}p?|0N~32t|9qw&;13P zuLgjbX$#pOz=wRL*WiOBl2$C1}Iq2R1Ftc?5d_pU!&j}3Bwj}pRlK1z~S1$mVnUw2g0CwtQ z&=uPJ0f2o;N?8}B;sBW0HUPfnH`k(rE!3C}0S5xuAhSK)tPdc0V`jbK?g3^d(}8ZB zFDN<4l5J7P8#C#nBLEx(KwD)h{T{&UN#34XAy%%K1o+Co9KfcrPAmTY2ua<%m&nZ4 z1MqPG@5(TF2!wq~NkS0-e9!;Uh_{_(p5I1tl{<);y%E5d0La*vLfVdAQ$i`=1Gogh znvucFcC>37172GI-U{H#8XQgN^AP}l^!vyKBg*=iAkm`0NjmuM@>&w4FDVj1q*142`Ob6>oz4RzjG4h0-#IE7n54hHbE*u0%2c_vCZ0Eqblp(>3409jC_kT(i2;&PJvrZ6fA0FPMv3xK|ZrlMo6 z4S-9*DF8ka7_5}=1}PDEsQ~N=AW5X69PC!$0g}}hftkG!!0#fqw4{q!DgX*vI=mb2 ziquvp;hT!I)UgpW(}6^R!}I72k_tWxQWOBU`zXSA+gM{g|E`GVJxJcvb3aMVo0-fL zp-a~<{4^Ad%1!~`MF4KeaM>kIAfwO&0DkB(I-_8|>wz5r$Rsn5hldM zcPq&kchv#90k0ouWD}C|KrI8HEht$M790RY*vTh6kM1EE41r9M06l1XJeq7qPfsWL zua*B#m4pQcU~K@a%)P;jhe_(;;ao}qK#uKDjLcdjRptqlsuLC*07?6!0scNpGU(7R z0l*q0|2Jy*ZRl>1j=r$yOnE*i6=_uA-%ioKdv(0Rf zV`%ImrU>EWAg$VxP*UleSiPmwoWJvz#yiGm@)2Lk_u$aqFRzr z7=ZHtydyC2BF4CwIr?S*uOJyZ53e%DT9Qx-05jA1US^1w1^_5xj*dBGT9=m5lSeJa*j3Kjr>m_8IAdI`>zQzjOku5@3UlT`Z1Ry1Y+{CSo_5<~eV`Uy{W zDM)#s@fngIY{mV@PF#vJiaiZ&+x)D03ZPQdosx`PNK&sqXOfwnnebNZ5RyuJkVHwx z(1wCkro-41B{5KTcNy`r$;|ZFZS(AkWh%(IqAZHXlawRpaq@f;GCGFxnD+Zb$)zCW zk)A-3c_KR%q?wH&^}$b+Kz7;J?Rg|*c8DeO==YJH1E92%DbG|t3*gY$O>pJHH8W+| zQexHEPczE(okv=I+9%-8~uv?R=(D6>)~@wy&lTy)F*R8EOW}RsR~>Q zq$riFBlK2EMU@~!Cxfr#FvuO=iZ|8jtGykChCY0B4G>aKEjr@qMaglYIHK5$`@X2f`d{&T-GoiPLUk+5Nm-`E z7do!BN`yKh0LGEB%t6o#@|>i7GFu-+Qi?o_MgqXE0lan;N&6aGkrKA%q^(q5A6?ny zxK2(PKLP;8m$tG2f)cmt5|xuX^luKt=_KWI2vTJL#+$fxg(S2|U3@nHg?UJ^%}pC$ z5ddvFZ!49swbMl`VU>1C-Yt)qo}=pVpLE1eN$NtkM3n$ooaEgNM(K2PT7jIRk_JU( z=t-z@Hiw?f#@0rg-xn`^eRGzcTU40~V>jkD%;~evVt>G41qd=*DAQKVcGM?BbJ|{g zK4H?>l4QVBc#GcEj`<@#N=kB7t{FWC-DcTV*3h<{xe}c|W7S~b-~covLV14<7{Q0! z6k4wjxg}>h?6Lq?)~yCW1Bm5M8NOTD%?HTJwfrf=a{zoRHMw3%rdQp9LKE@SXaYgX zdit1WU#O?F>xrV28f<$2dhV}u337^W=xHNoea=>94c({}X7U>Vy1mM)b;nRjHDzw2 zm4MvAuMXv_>wwjPiCyL}0nmke1?o-!6kuYPCME#7aIZk!34j7j?9#*pKo{;6s5=2r dfQen2_&-w1q>l3jGwlEX002ovPDHLkV1ndW%I*LF diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xxxhdpi/ic_star_white_36dp.png b/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xxxhdpi/ic_star_white_36dp.png deleted file mode 100644 index 74b1c0bbcdd27424e459386a547066cc97181b48..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1306 zcmV+#1?BpQP)5Y!=xmnq9QhH znbk>}7+o9L?b?bk!&w)OIny1Fd*1D)Tj%b%yXW8ie&65o`@UY1Wm%SGS(as4mgRF4 zP#~*me8K0knnG7DQB7A_k?|(AX32_#AZ64#K~Pp>jHb>gS&gxaIy+@GL_YQ1CF@$O zroL6O>Z3FNP~Sy5$@(8NXy|2Gbx7kV4ILp()>S-8B#+3tiXVt%tE^h|AxtD;?vN$1 zjHbLVi!3hCl=F0u-!Yx$OqIV8;2_QUUH--}T5`Yqj4ia}8~GQ#sG%h_+$z6fG0|8c zf8u)15{(Ml%a3@DXibnGQA)J-%YP^&7DMDWY$6sLmK<5UlOl1N`2hKCE`^@7o|GhA`!1EwQ(IMNyIVI zmC6`Hq8?XjB1jJT6qBgk|_(mnZxsp zqL9916OcK`qBnyW!6as}l=W<AJ0qlmREWCr6I&H!$qo#MwdHdD+IE|AJ9IYKd;nWk8g$yWTLh)l(f zAgl0$l>`+pUL#B@3o}!RVGPxzswze+aSY)MspvF=l^o=9h*a|@y_Gz4VGpTfH`z)q z+Od&T@fB^Ad<0lPDwsz=t%b>4!OKESRO{j)D)FlGJgC-3K4o~(3G(C&dQgJb?5De& zLncLd$+vWnvk0;huUJmH+`vr2m@dpKau1`a!emv9lDinf8BBGGL2@6t9Ku8g=_NCe z&2CJyi_S6!ZP|cH*3njGA;5f0@eTo*h)INSTZjoV7sIH;P3L()W+RU>+;WV5G9NuC z!41EXBX1y+BAm8`40#7ZKE+8NlP+&z3Qn3RuVEcdS|hLF7*0AYZ=okn>n1N@EKVCK zFJT!@TOu!^1gGtlcaTL5P79GCuV5H%7%Hz|E^e4DuV4pm*d}iv$VJ>xNxHm%0^HJH zUcgJZWxBk8uW`#pc>#apmNJ=-T-QC%9>aOveEdag?JZ;y0O%F2t{z zxwNM}bEzg?VQ!Ggc#L>$C0G9;m+y(!!!j9*h{th8ssToFoOsNW$tWgPmzht7A|QkL zTqah#WG>osiP&tXkD5Urek3+kw2`SOBo<|iQ4|= diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xxxhdpi/ic_twitterlogo_18dp.png b/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xxxhdpi/ic_twitterlogo_18dp.png deleted file mode 100644 index 8494adc7486a7d656cc8745adbb4f32d15544eda..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1407 zcmV-_1%UdAP)NklD#w zV6~*RWf1(wyZMON3OEBe4>$?fW56YY^Cs{p@Q|dRHsx+a>;tTo^nD*di*isz>@Df* zJ_q*UjfjJQ<-nW{a-Rd2N_u*bHzM{2E&)yjW=Z<9gRb=~*#Jev0l=&NcO|{o=g=;^ z5n)iC7>by2qJIHb11|t4q{urN*cMnK$zb=Q5`eA+t^@qfog(SuUMG)Wj)+5nmw{a? z>3kMgAjtssq7r~!0gg|n8Kg5MebDRlDVQU||MqLZQKh<){ObvvH4)(izeArtGYg=I zu-rGm?gLJI2P~4bvc$27I3KvbM8|;aQ@}ldBXWNFTu&U8%!kj-1!#NV7rnxi=s$Fhq*Z-%=K|!?`y;T^CJu8HUEbFiSH+amhJo+n z(^R}`QVMvZuc9n~d`Lt%-#BzPMZTll4%{N?hc1Ri#5~~nF1$T>Rs#zqz0*TxJ!=U- z%hLT~+_?^I^DdY4q6%$xLqxDfO{pm&B&#N@o`58{W5ph=fENVKI?a+|mO_JPtWK%9cdjcONVBGi2 z1a3%Uh-_mfAh=YL3q&^M0_1y&tJ7}TCvP6X`I7txW>XeGDM7mdJT3d=$qV`>J+pKi zQv%RMX;w3NSMyF-CF$Ti(G5~10PP5T2zV|u9ch`QrKN^t1C&0q1%Nwd_h!?LJbrnm z)TknW5=gFIMWwXVi7Dh=K=za5!G00d0LVJ70X$*crpTIl!gou$tcJQ`fYuq2X2`b! zN7pc{K!>Z>k+tUJRRBsW73*?C>b!Ktm{CZ9|6@g0Nb>Ez3e^H=A@F1si^`OEC_h`$ zTV-lCNY?_$xyMEwQOmGQJ;mMo@VQk0O4ZDJtQXS`D$~CrF^^Rbm*lZYHL3AsGd3sX9O@lH3FN0(tfntD&k6 zq=B6`yIw<8r-=Zx&dP}J2=#R52H;rWkjfr-R6DnJ-WjxUq5!36a-X)u>mOj}iU55A z%#-wKMV2;2GU7z(4%HgdJP(*m!>YMDv>A_R&Gad&PPqjA6 z0bra=Z{s6kFTgLk{19nIdJy25&|zt(KDXkQW6=Je)70Ru+`a!eFsAxF2cTKN%?XUn z(Y?ELyN$U@lJm}HrQQIg#A`V>rB_yZi7}Pa2F3&T8zuQI(iWjCfCep%h}r3ZCowjTF1tN}Czum(^AsOL8| z#NHyPMNo^NG6Y%#mEqTNP>Y}zL1hTE2r9#`<)9WpErQArXc1I~-#?zHR#-*EY%>4= N002ovPDHLkV1jlZqO|}3 From 937bd7ffd00d7c4185a915ba8c3c8f1e5b5b28fd Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Thu, 14 Jul 2016 20:13:40 +0300 Subject: [PATCH 030/414] feat(android): Updated DialogView --- .../main/java/im/actor/sdk/ActorStyle.java | 10 ++++++ .../controllers/dialogs/view/DialogView.java | 32 ++++++++++++++++-- .../res/drawable-hdpi/ic_group_black_18dp.png | Bin 0 -> 319 bytes .../res/drawable-hdpi/ic_lock_black_18dp.png | Bin 0 -> 315 bytes .../res/drawable-mdpi/ic_group_black_18dp.png | Bin 0 -> 194 bytes .../res/drawable-mdpi/ic_lock_black_18dp.png | Bin 0 -> 234 bytes .../drawable-xhdpi/ic_group_black_18dp.png | Bin 0 -> 327 bytes .../res/drawable-xhdpi/ic_lock_black_18dp.png | Bin 0 -> 349 bytes .../drawable-xxhdpi/ic_group_black_18dp.png | Bin 0 -> 442 bytes .../drawable-xxhdpi/ic_lock_black_18dp.png | Bin 0 -> 545 bytes .../drawable-xxxhdpi/ic_group_black_18dp.png | Bin 0 -> 568 bytes 11 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-hdpi/ic_group_black_18dp.png create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-hdpi/ic_lock_black_18dp.png create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-mdpi/ic_group_black_18dp.png create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-mdpi/ic_lock_black_18dp.png create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xhdpi/ic_group_black_18dp.png create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xhdpi/ic_lock_black_18dp.png create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xxhdpi/ic_group_black_18dp.png create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xxhdpi/ic_lock_black_18dp.png create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xxxhdpi/ic_group_black_18dp.png diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorStyle.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorStyle.java index 6dd2be1b46..6c38382f85 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorStyle.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorStyle.java @@ -358,6 +358,16 @@ public void setDialogsTitleColor(int dialogsTitleColor) { this.dialogsTitleColor = dialogsTitleColor; } + private int dialogsTitleSecureColor = 0xff559d44; + + public int getDialogsTitleSecureColor() { + return getColorWithFallback(dialogsTitleSecureColor, getDialogsTitleColor()); + } + + public void setDialogsTitleSecureColor(int dialogsTitleSecureColor) { + this.dialogsTitleSecureColor = dialogsTitleSecureColor; + } + private int dialogsTextColor = 0; public int getDialogsTextColor() { diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/view/DialogView.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/view/DialogView.java index f3330dd89c..7c300bda0d 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/view/DialogView.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/view/DialogView.java @@ -53,6 +53,7 @@ public class DialogView extends ListItemBackgroundVieweH;sPQAkvH&~=c@v?AB+K}rafEKjTcltlMh(%x70ob^Ax;F;yu*@+Nb(^l zxcu=%I!sL;$Jw_ zv?5z}jRMQABAN3Dhnic+mOY@rvIj(%^I*U%3jyK;GI0nI=7fwk4MqV$00310%ILvl R%Z2~|002ovPDHLkV1i~fc*Ot! literal 0 HcmV?d00001 diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-hdpi/ic_lock_black_18dp.png b/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-hdpi/ic_lock_black_18dp.png new file mode 100644 index 0000000000000000000000000000000000000000..ef523001af3fbcc39ba8755aa15c137816c407e4 GIT binary patch literal 315 zcmV-B0mS}^P)*Bi`Nfo4!~(=$p@!MwP~!}h{{qC!lvpN%6gR9m)NmrH zk*35l1tf#;`5vZ5VNhEJ3is(i`~yc)CxpKP@l+sYBF3^PGA%*J;lx;0PbJIhiLtDK zN|rSYImQ3!53i(8Rm*CrWLXWdjT=fW0>!5lvDHi{5N`wG17u*3zF>wC1pt~5E=F)$%On5* N002ovPDHLkV1n3#eVzaS literal 0 HcmV?d00001 diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-mdpi/ic_group_black_18dp.png b/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-mdpi/ic_group_black_18dp.png new file mode 100644 index 0000000000000000000000000000000000000000..735614edd98130a7dbf558bfa4048385db2f585b GIT binary patch literal 194 zcmeAS@N?(olHy`uVBq!ia0vp^LLkh+1|-AI^@RhePEQxdkcwMxFC64`FpywTWZ5V!P6{|Xa6+sc#Wm6`FEjJ`eh7MkI}pkB+xeMaCm&q~3S zy-VUf>~HdL9=_mHm1unAYlG0eU!PA-ob1oHpoy<)ZL3gC?J|Qe;YBx2$lqBzp^^Qt sbN$oYmbjoyp1*>bo=eTX)W@W<2l9Fp_kDcv3+N~YPgg&ebxsLQ0LgAk`Tzg` literal 0 HcmV?d00001 diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-mdpi/ic_lock_black_18dp.png b/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-mdpi/ic_lock_black_18dp.png new file mode 100644 index 0000000000000000000000000000000000000000..3dab038e42d3c8e7d6258e1bd2af2c9ca763ab53 GIT binary patch literal 234 zcmeAS@N?(olHy`uVBq!ia0vp^LLkh+1|-AI^@Rhejh-%!Ar-fh6Ati4G~D3m@NJke zwbADR>yh&fKkP;9o4Y2mlr;WO7kJNe8)wLR_6WaPHFJr? z7V(bjiGuf8rYcLxFg!AiIAZSbl%q*b;a{`JfgJ`OzT$Sx9fmAz^^c8T*murQY0hK* zDC@<`v{Y727U*~ePgg&ebxsLQ0EiV-+5i9m literal 0 HcmV?d00001 diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xhdpi/ic_group_black_18dp.png b/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xhdpi/ic_group_black_18dp.png new file mode 100644 index 0000000000000000000000000000000000000000..792d67d93ee1b684b36a290922aabf8c9d3631f4 GIT binary patch literal 327 zcmV-N0l5B&P)0{Df+P3|7Ob%27?zxcJ4o!tJ|RPsH^lQiwy zuW!^!m0iAO>B430ykw2O$C5Yj!QzE}q z>j)*nT*?2K zOB(Jp@=r8geAJ@q?5dh?Zd)_&u{BV}b>o|x(ad|?3>LZtX6xo7-K4AT?D!XA7=~fu Z$O95TAr;jtr0DH*+)ga^XORU>Fa%@J200xfc`yeL@buxWEkPkHnKIZ*MEsmVC5)4yGB>aQ zeb5u9b|ueGqJ}NzWV0U-Oz}f*3EW9fu{3(~%dcLM{1%7MkZjNo zkUt>#BMzYn*`O03zf1BbMkoxSB(Q-S;1UeVMZD8e5wd?M;>T7Iav6nO6`?0HM#u=g z@y3y<;GkP*4N{~g4IG_9RY_f4r%+r{m(VFxlhieI3OS;0-jM+t-GA8;%mgQM4;i5- v3tgf%4{=V6QxzeLVWxJ4jM7YPB9id}=A;=w>1~2?00000NkvXXu0mjfsR59l literal 0 HcmV?d00001 diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xxhdpi/ic_group_black_18dp.png b/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xxhdpi/ic_group_black_18dp.png new file mode 100644 index 0000000000000000000000000000000000000000..4fe1b2a6ed064ae1adfc50ed3cf0db44295d40d4 GIT binary patch literal 442 zcmV;r0Y(0aP)vlZH81gmq}aTetd+`g7^vjaDU&)h>JbKN z_;f5%hlVw%i`_&R=sJN8jY5Qh@(FZkv>v_pA%>}#S3|cu^cQp`V3Px#07*qoM6N<$g4(UZSO5S3 literal 0 HcmV?d00001 diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xxhdpi/ic_lock_black_18dp.png b/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xxhdpi/ic_lock_black_18dp.png new file mode 100644 index 0000000000000000000000000000000000000000..507e39b7939642aa28d1c554be11b898a52813df GIT binary patch literal 545 zcmV++0^a?JP)|36ov;kTyRBD1RJq8h#-iBlv*fh)JmVity0kh5j(**uo6T`6|@xuMR5TeQB1F* zB1sfU{4cqIFy4ui$s}_wo&z7GnETK9GGuaQF$^xQ-132WumcVy;`1En(@`i0E(XGhwc`4>sG*s;%r%sXM_fjmNXvoR`P^}^-z+x=+ z&&0$S6b3PnDq`3z6tS;I5V9E0ik3Pnql^`&Zp(!UNM%`dQ!GYE?7>a36d|#cn__8V zuo#Q67>nsE_9X9%j7r34^sKwsCFoG(iL2~uUBx~@PoB(P@TH^JzKz_7j$)w_4%eQF zZQ01()=}&ZROHF51oyh06=(BgF6ex^7nd08rn|c0;owG*mqKJFkA%FnAV~5^#bPYR zVk|~gEKW%3!%eXxLSj)j#fHH*0V&bLklPO?ri|b3WR#OvFj1;UNv&W8EE$=1G0K71 j*R+v&LxGEni%9(d}M{P)L|=s+4E&0K7q;LL{8HpnU@l5*JY^>6tjt_6g9Ih*qP% z+3q-C+uiMaJG1itCBI|;`=2k;N}k|nZ1*2yB7AybCu zvocK<$W|Ax$Vmh9SxJzu9?DB=^sL#d1;lLFd(1(H>VJwy!1kIK09kYl$XXe z=ChMkuz-Dy`RtSwEZ|mSK06Hs3#e+$XQ$4*fO^yiR8+8l8;$wwWE3o5Ph&niDFq8y z)R-R%*FBU!Hz1##De@&i`3gC1Kz=C1eJ(=0S>_GO4{ayqJ(SM{JVbKmhsFwdNIsD7 zj`!pNnT$X_00000z8NA{$uxOHUXo?2{m(+vvF2QU<(yY^+WE|2z-O*InJuRLp6GWT zzDwRII82I+9p_-$=c9+XuYP09_syKuzTd4V7)qz}lX%9g$4*I~w~l`EJ(tKc4#iFJ zRZV#4x5=FE${2Z~VK6^ECP%w;J!Q`SbJsi>FdXS4xzwSr`1kdbG5^;sUc;c_NW}&I za);h$%=tDWG1HynXAPndq4hR4M000000000W;(P%pv!=h5MlBxz0000 Date: Fri, 15 Jul 2016 09:20:54 +0300 Subject: [PATCH 031/414] feat(android): Showing channel and bot icons in dialogs list --- .../controllers/dialogs/view/DialogView.java | 21 ++++++++++++++++-- .../drawable-hdpi/ic_megaphone_18dp_black.png | Bin 0 -> 311 bytes .../res/drawable-hdpi/ic_robot_black_18dp.png | Bin 0 -> 452 bytes .../drawable-mdpi/ic_megaphone_18dp_black.png | Bin 0 -> 240 bytes .../res/drawable-mdpi/ic_robot_black_18dp.png | Bin 0 -> 342 bytes .../ic_megaphone_18dp_black.png | Bin 0 -> 392 bytes .../drawable-xhdpi/ic_robot_black_18dp.png | Bin 0 -> 541 bytes .../ic_megaphone_18dp_black.png | Bin 0 -> 538 bytes .../drawable-xxhdpi/ic_robot_black_18dp.png | Bin 0 -> 856 bytes 9 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-hdpi/ic_megaphone_18dp_black.png create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-hdpi/ic_robot_black_18dp.png create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-mdpi/ic_megaphone_18dp_black.png create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-mdpi/ic_robot_black_18dp.png create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xhdpi/ic_megaphone_18dp_black.png create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xhdpi/ic_robot_black_18dp.png create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xxhdpi/ic_megaphone_18dp_black.png create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xxhdpi/ic_robot_black_18dp.png diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/view/DialogView.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/view/DialogView.java index 7c300bda0d..abc7064724 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/view/DialogView.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/view/DialogView.java @@ -62,6 +62,8 @@ public class DialogView extends ListItemBackgroundViewBnFP=Qea9iRg$FglP9%$?aclQF`+7s-$8*CrqL?B3+v zB%{H~8f4MJQr+$YL*O8RI&2&R6JQDmb_R6sC$MfoT%x)zR%Mv13Dc|pRM9X^J2GoD zz3lOp3noELFLC*wT5n-%U;ylZ2e407_dK(cS+xSxA6r<;+t}I|``)G>u?syWU%$X&y z0E!~H*(vyIg)9|ZcDpwVLSg!l_R3Lh`mJjJQkM=;$8W(O!k$VKKpcP^7T^E?002ov JPDHLkV1f~2gPi~X literal 0 HcmV?d00001 diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-hdpi/ic_robot_black_18dp.png b/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-hdpi/ic_robot_black_18dp.png new file mode 100644 index 0000000000000000000000000000000000000000..72479f533a0136464c981bb69ec6ae897e4b4d5e GIT binary patch literal 452 zcmV;#0XzPQP)6uQ<n%uV?GjT%<>rkoPr17*y4>YHJrcRdp% z0vp&*@M=gITEo`9Si+gnqs(OWZS7>ysiEkFd3X79ns|BIV=W(wd0z`|g) u+q&+wd6Zek9I9HFvQTeYi+Yv)3-$xG6ipn3Nf)gE0000djnU~Ypzt2_A4(Eyfn+@;3+_z1Su~q&;d_C8OrRhwGKMW4Wu&kut|Em quAr_YgM&(rs3#KCEat7P0~!F}na8of!p%D2BbgcMsS%`&`7Ed%j6p{SB+1Rk?XAt)UVlhN!n$} zkv>}H%80B{00{{R307*qoM6N<$g3r^A1poj5 literal 0 HcmV?d00001 diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xhdpi/ic_megaphone_18dp_black.png b/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xhdpi/ic_megaphone_18dp_black.png new file mode 100644 index 0000000000000000000000000000000000000000..78f85bd66c6861d758e301731bd6d67e474ef6ac GIT binary patch literal 392 zcmV;30eAk1P)q(`&Bbz6)E z#I8LJq$0EsE1yAuNQGM<6@8)@C@e^TN+TO6R*vjtAGg7&nscQd=y%pWQm>UHigC2krJR)Bbw&!ihJ!ISr_Qmz mI=aF&&sCt5vTEqh89D>Y5g-CYz(s%v$bb-JK)tJJ)pcfeX8X3>meN)Gt`U-3?j67@%w9L^jf|d?1AYEJAa`b?MkqItwaHcwLi4$ujb9@qncv*z z*XhJ6VM{1qaqN=UQu#v)`I1Q)xvAwnW}BHBDz%kup^~Yg?Q{ly0$jGplT4sS_J@#a z!U!qkPqve0j8uATB$QhtOgXPNq51x?e(OU>8;gMAv!#!^n))!!58+t<7^VmB63gRj zf}7(s&s54EQ6jXF=8sfT%v&`=$oG+kteV@@`zDqnBodm&cBN3Vt`(ABKnmYmC561I zEtoT$9HEknH1g#-g_pmDPzz>}P*+dbf2x0NICk2*v`KAJm)7|!M=2+C6^RmRQ8ARW zD4~+u!nomu>G-m;%4hRM&Qhs*e;}HiixLvfsP5FGE7|A><>t{Hp!Z-XhUlTt#zPOr fK}mS9(JAx=_JL(3h7mHO00000NkvXXu0mjfRk`<6 literal 0 HcmV?d00001 diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xxhdpi/ic_megaphone_18dp_black.png b/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xxhdpi/ic_megaphone_18dp_black.png new file mode 100644 index 0000000000000000000000000000000000000000..1368361424019afb3bd304885aa0c157c6330214 GIT binary patch literal 538 zcmV+#0_FXQP)pGWUM|VlwZw)ktXic7DzFnNx3#QJ*& z^N3k%sqYQ3nX_%-oPvk#ehXHV zW2KG~)#uW}6PGgu%Vqf+PH2%W(8m+Qq}7{(<$JUD02UkMFejcB^1C-7=%irZl8)9m zPhNiY0yebMMxJ+)elm!mvajJRp2MKAW*V_gQ9!2gL58=P=j3C&VBnX$jIuqbpfm-e z{U=E`7LFu!a5lN?0AvnUGih^4hvT_ihx(^<0Gb30hsYu1wr%9w1!@R1m25hlOa>O6 zh$|8tNOJ`sWXs@UWpM>WTOV`YXd)vTr_tH1w>#+K4sx*Ku4<&9N34-R6uaROEAFaB c3NAKcf7_&SH#y{z@Bjb+07*qoM6N<$f)2v<-2eap literal 0 HcmV?d00001 diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xxhdpi/ic_robot_black_18dp.png b/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xxhdpi/ic_robot_black_18dp.png new file mode 100644 index 0000000000000000000000000000000000000000..05da3209878b6a389dc436429634447511e046fc GIT binary patch literal 856 zcmV-e1E>6nP)>Y5g-CCf;$l)0z^PPRXb{`t9Q41c1PK*ovN$* ze)^l~xBuRr-V^O~o$M5s4q#jJY!8n;lyoI&i0_i#CVw+2-UHZYNw<paA!HYrw!lzTJ>? zYJmNebWv5pO?fO+j5Yxbg1%az;`IZA@|~n_)n~l^?+My^3gZ1_KD;x8X~wH5*&dyT zRe&6$NYsl;Nse_}{{mPy<)=12fK}EZw4M#*OE|q4IO+FFuEE%FA7^kDB+ePB(9L>` z3U}`C5`_C)()ESlDEs82C3JZ2rm3W>cK4-Iu$yAdUk%q4yAQxF4W6>!l};vPyR65i zI(N9q@>NKQN(kj?vAmTc@hmW}p+$)=LoSdPOBT6Aet98X8t2gdSzzSYS7&DkmqfQ+ zd`O8(=+Tfzme3Ko)-%Uwm);m{N$S$jdJ1L0m5u}>QKaex=y2PW^%xu1dBhN=^o3sQ zlmUfoF%qxoAM2qIIgDXLD+}z0DfQ(tSq@vuWwk!#5?c1Aurg}_YdJd{iY=SBkaX&<6;Y~($GnmDoGxYLJtr6bz6Y@I z;XMU=0;59CJ+dVPDH5x%p1>kq$RrmnLE94;mqPv^SljV4BO$NoEyK6s4A7pyrn-?` zJe1ho)0W-l*a0k46r!>{i^te-wbm^)1dW11t|u_|2i({!kTPFChtB9dE882`tOC6R i4q&}3+iTz5dh8FrBc?ZD;gVzk0000 literal 0 HcmV?d00001 From f64aa4239392ebc2ab804a7dcb0170286dbf7d8f Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Fri, 15 Jul 2016 09:32:22 +0300 Subject: [PATCH 032/414] fix(android): Fixing bot icon padding in DialgoView --- .../java/im/actor/sdk/controllers/dialogs/view/DialogView.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/view/DialogView.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/view/DialogView.java index abc7064724..155b41089b 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/view/DialogView.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/view/DialogView.java @@ -365,7 +365,7 @@ public void onDownloaded(FileSystemReference reference) { } else if (arg.getPeer().getPeerType() == PeerType.PRIVATE) { if (arg.isBot()) { res.setTitleIcon(botIcon); - res.setTitleIconTop(Screen.dp(34)); + res.setTitleIconTop(Screen.dp(33)); maxTitleWidth -= Screen.dp(16/*icon width*/ + 4/*padding*/); } } From 91ca05dabaec22cd88032ee6abb2e5cea6d0a0e8 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Fri, 15 Jul 2016 09:34:08 +0300 Subject: [PATCH 033/414] feat(android): Faster contacts opening --- .../im/actor/sdk/controllers/DisplayListFragment.java | 8 +++++--- .../sdk/controllers/contacts/BaseContactFragment.java | 3 ++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/DisplayListFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/DisplayListFragment.java index 66986e2aab..b207a49b53 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/DisplayListFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/DisplayListFragment.java @@ -2,6 +2,7 @@ import android.app.Activity; import android.support.v7.widget.CustomItemAnimator; +import android.support.v7.widget.DefaultItemAnimator; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; @@ -56,7 +57,8 @@ protected void afterViewInflate(View view, BindedDisplayList displayList) { public void setAnimationsEnabled(boolean isEnabled) { if (isEnabled) { - CustomItemAnimator itemAnimator = new CustomItemAnimator(); + DefaultItemAnimator itemAnimator = new DefaultItemAnimator(); + // CustomItemAnimator itemAnimator = new CustomItemAnimator(); itemAnimator.setSupportsChangeAnimations(false); itemAnimator.setMoveDuration(200); itemAnimator.setAddDuration(150); @@ -128,9 +130,9 @@ public void onResume() { @Override public void onCollectionChanged() { if (displayList.getSize() == 0) { - hideView(collection); + hideView(collection, false); } else { - showView(collection); + showView(collection, false); } } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/contacts/BaseContactFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/contacts/BaseContactFragment.java index d938e4aac8..eaaf6ac1dd 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/contacts/BaseContactFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/contacts/BaseContactFragment.java @@ -70,6 +70,8 @@ protected View onCreateContactsView(int layoutId, LayoutInflater inflater, ViewG } } + setAnimationsEnabled(false); + View headerPadding = new View(getActivity()); headerPadding.setBackgroundColor(ActorSDK.sharedActor().style.getMainBackgroundColor()); headerPadding.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, useCompactVersion ? 0 : ActorSDK.sharedActor().style.getContactsMainPaddingTop())); @@ -77,7 +79,6 @@ protected View onCreateContactsView(int layoutId, LayoutInflater inflater, ViewG addFootersAndHeaders(); - if (emptyView != null) { if (messenger().getAppState().getIsContactsEmpty().get()) { emptyView.setVisibility(View.VISIBLE); From e50d5dbc4e9e658bcfc1fdae0876ee644ac0f52a Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Fri, 15 Jul 2016 15:19:26 +0300 Subject: [PATCH 034/414] feat(android): Restyle group info page with channels support --- .../main/java/im/actor/sdk/ActorStyle.java | 10 + .../conversation/ChatFragment.java | 23 +- .../toolbar/ChatToolbarFragment.java | 12 +- .../controllers/group/GroupInfoFragment.java | 189 ++++++++----- .../group/view/MembersAdapter.java | 3 +- .../controllers/profile/ProfileFragment.java | 49 ++-- .../ic_info_outline_black_18dp.png | Bin 0 -> 475 bytes .../res/drawable-hdpi/ic_menu_black_18dp.png | Bin 0 -> 122 bytes .../ic_info_outline_black_18dp.png | Bin 0 -> 294 bytes .../res/drawable-mdpi/ic_menu_black_18dp.png | Bin 0 -> 88 bytes .../ic_info_outline_black_18dp.png | Bin 0 -> 566 bytes .../res/drawable-xhdpi/ic_menu_black_18dp.png | Bin 0 -> 103 bytes .../ic_info_outline_black_18dp.png | Bin 0 -> 861 bytes .../drawable-xxhdpi/ic_menu_black_18dp.png | Bin 0 -> 119 bytes .../src/main/res/layout/fragment_group.xml | 6 - .../main/res/layout/fragment_group_header.xml | 254 +++++++++--------- .../main/res/layout/fragment_group_item.xml | 18 +- .../src/main/res/layout/fragment_profile.xml | 3 - .../src/main/res/menu/group_info.xml | 11 +- .../src/main/res/values-ru/ui_text.xml | 4 +- .../src/main/res/values/ui_text.xml | 11 +- .../core/modules/presence/PresenceActor.java | 8 + .../java/im/actor/core/viewmodel/GroupVM.java | 33 +++ 23 files changed, 375 insertions(+), 259 deletions(-) create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-hdpi/ic_info_outline_black_18dp.png create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-hdpi/ic_menu_black_18dp.png create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-mdpi/ic_info_outline_black_18dp.png create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-mdpi/ic_menu_black_18dp.png create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xhdpi/ic_info_outline_black_18dp.png create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xhdpi/ic_menu_black_18dp.png create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xxhdpi/ic_info_outline_black_18dp.png create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xxhdpi/ic_menu_black_18dp.png diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorStyle.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorStyle.java index 6c38382f85..a7680696eb 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorStyle.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorStyle.java @@ -510,6 +510,16 @@ public void setTextSubheaderInvColor(int textSubheaderInvColor) { this.textSubheaderInvColor = textSubheaderInvColor; } + private int textDangerColor = 0xffe44b4b; + + public int getTextDangerColor() { + return textDangerColor; + } + + public void setTextDangerColor(int textDangerColor) { + this.textDangerColor = textDangerColor; + } + //Settings private int settingsMainTitleColor = 0; diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatFragment.java index ea7ca53091..63eb29089f 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatFragment.java @@ -174,15 +174,21 @@ public void onResume() { goneView(inputOverlayContainer, false); showView(inputContainer, false); } else if (isMember) { - inputOverlayText.setText("Can't send message"); + if (messenger().isNotificationsEnabled(peer)) { + inputOverlayText.setText(getString(R.string.chat_mute)); + } else { + inputOverlayText.setText(getString(R.string.chat_unmute)); + } inputOverlayText.setTextColor(style.getListActionColor()); - inputOverlayText.setClickable(false); + inputOverlayText.setClickable(true); + inputOverlayText.setEnabled(true); showView(inputOverlayContainer, false); goneView(inputContainer, false); } else { inputOverlayText.setText(R.string.chat_not_member); inputOverlayText.setTextColor(style.getListActionColor()); inputOverlayText.setClickable(false); + inputOverlayText.setEnabled(false); showView(inputOverlayContainer, false); goneView(inputContainer, false); } @@ -200,6 +206,19 @@ public void onOverlayPressed() { execute(messenger().unblockUser(userVM.getId())); } } + } else if (peer.getPeerType() == PeerType.GROUP) { + GroupVM groupVM = groups().get(peer.getPeerId()); + if (groupVM.isMember().get()) { + if (messenger().isNotificationsEnabled(peer)) { + messenger().changeNotificationsEnabled(peer, false); + inputOverlayText.setText(getString(R.string.chat_unmute)); + } else { + messenger().changeNotificationsEnabled(peer, true); + inputOverlayText.setText(getString(R.string.chat_mute)); + } + } else { + // TODO: Rejoin + } } } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/toolbar/ChatToolbarFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/toolbar/ChatToolbarFragment.java index cf81b452ce..d63856979f 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/toolbar/ChatToolbarFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/toolbar/ChatToolbarFragment.java @@ -204,7 +204,9 @@ public void onResume() { bind(barSubtitle, barSubtitleContainer, group); // Binding group typing - bindGroupTyping(barTyping, barTypingContainer, barSubtitle, messenger().getGroupTyping(group.getId())); + if (group.getGroupType() == GroupType.GROUP) { + bindGroupTyping(barTyping, barTypingContainer, barSubtitle, messenger().getGroupTyping(group.getId())); + } } // Show/Hide Avatar @@ -239,13 +241,19 @@ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { // Show menus for leave group and group info view if (peer.getPeerType() == PeerType.GROUP) { - if (groups().get(peer.getPeerId()).isMember().get()) { + GroupVM groupVM = groups().get(peer.getPeerId()); + if (groupVM.isMember().get()) { menu.findItem(R.id.leaveGroup).setVisible(true); menu.findItem(R.id.groupInfo).setVisible(true); } else { menu.findItem(R.id.leaveGroup).setVisible(false); menu.findItem(R.id.groupInfo).setVisible(false); } + if (groupVM.getGroupType() == GroupType.GROUP) { + menu.findItem(R.id.clear).setVisible(true); + } else { + menu.findItem(R.id.clear).setVisible(false); + } } else { menu.findItem(R.id.groupInfo).setVisible(false); menu.findItem(R.id.leaveGroup).setVisible(false); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java index 6ca5670e0b..efd92dc619 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java @@ -26,12 +26,14 @@ import java.util.ArrayList; import im.actor.core.entity.GroupMember; +import im.actor.core.entity.GroupType; import im.actor.core.entity.Peer; import im.actor.core.viewmodel.CommandCallback; import im.actor.core.viewmodel.GroupVM; import im.actor.core.viewmodel.UserPhone; import im.actor.core.viewmodel.UserVM; import im.actor.runtime.actors.messages.Void; +import im.actor.runtime.mvvm.ValueListener; import im.actor.sdk.ActorSDK; import im.actor.sdk.ActorSDKLauncher; import im.actor.sdk.ActorStyle; @@ -44,6 +46,7 @@ import im.actor.sdk.util.Screen; import im.actor.sdk.view.TintImageView; import im.actor.sdk.view.adapters.RecyclerListView; +import im.actor.sdk.view.avatar.AvatarView; import im.actor.sdk.view.avatar.CoverAvatarView; import im.actor.sdk.util.Fonts; @@ -68,7 +71,7 @@ public static GroupInfoFragment create(int chatId) { private GroupVM groupVM; private RecyclerListView listView; - private CoverAvatarView avatarView; + private AvatarView avatarView; private MembersAdapter groupUserAdapter; private View notMemberView; @@ -95,8 +98,8 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa listView = (RecyclerListView) res.findViewById(R.id.groupList); notMemberView = res.findViewById(R.id.notMember); - res.setBackgroundColor(style.getBackyardBackgroundColor()); - listView.setBackgroundColor(style.getMainBackgroundColor()); + res.setBackgroundColor(style.getMainBackgroundColor()); + // listView.setBackgroundColor(style.getMainBackgroundColor()); notMemberView.setBackgroundColor(style.getMainBackgroundColor()); ((TextView) notMemberView.findViewById(R.id.not_member_text)).setTextColor(style.getTextPrimaryColor()); @@ -106,36 +109,56 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa // Views View header = inflater.inflate(R.layout.fragment_group_header, listView, false); - avatarView = (CoverAvatarView) header.findViewById(R.id.avatar); + avatarView = (AvatarView) header.findViewById(R.id.avatar); + avatarView.init(Screen.dp(48), 22); + TextView aboutTV = (TextView) header.findViewById(R.id.about); View aboutCont = header.findViewById(R.id.aboutContainer); + View addMemberCont = header.findViewById(R.id.addMemberCont); + View leaveCont = header.findViewById(R.id.leaveChannelCont); + TextView title = (TextView) header.findViewById(R.id.title); - TextView createdBy = (TextView) header.findViewById(R.id.createdBy); View descriptionContainer = header.findViewById(R.id.descriptionContainer); SwitchCompat isNotificationsEnabled = (SwitchCompat) header.findViewById(R.id.enableNotifications); TextView memberCount = (TextView) header.findViewById(R.id.membersCount); - TextView settingsHeaderText = (TextView) header.findViewById(R.id.settings_header_text); - TintImageView notificationSettingIcon = (TintImageView) header.findViewById(R.id.settings_notification_icon); - TextView membersHeaderText = (TextView) header.findViewById(R.id.membersTitle); + // TextView settingsHeaderText = (TextView) header.findViewById(R.id.settings_header_text); + // + // TextView membersHeaderText = (TextView) header.findViewById(R.id.membersTitle); // Styling - ((TextView) header.findViewById(R.id.about_hint)).setTextColor(style.getTextSecondaryColor()); + // ((TextView) header.findViewById(R.id.about_hint)).setTextColor(style.getTextSecondaryColor()); header.setBackgroundColor(style.getMainBackgroundColor()); + header.findViewById(R.id.avatarContainer).setBackgroundColor(style.getToolBarColor()); title.setTextColor(style.getProfileTitleColor()); - createdBy.setTextColor(style.getProfileSubtitleColor()); aboutTV.setTextColor(style.getTextPrimaryColor()); - // themeHeader.setTextColor(style.getProfileSubtitleColor()); - memberCount.setTextColor(style.getTextHintColor()); - settingsHeaderText.setTextColor(style.getSettingsCategoryTextColor()); - notificationSettingIcon.setTint(style.getSettingsIconColor()); - membersHeaderText.setTextColor(style.getSettingsCategoryTextColor()); - ((TextView) header.findViewById(R.id.settings_notifications_title)).setTextColor(style.getTextPrimaryColor()); - header.findViewById(R.id.after_about_divider).setBackgroundColor(style.getBackyardBackgroundColor()); - header.findViewById(R.id.after_settings_divider).setBackgroundColor(style.getBackyardBackgroundColor()); + memberCount.setTextColor(style.getProfileSubtitleColor()); + // settingsHeaderText.setTextColor(style.getSettingsCategoryTextColor()); + + ((TintImageView) header.findViewById(R.id.settings_notification_icon)) + .setTint(style.getSettingsIconColor()); + ((TintImageView) header.findViewById(R.id.settings_about_icon)) + .setTint(style.getSettingsIconColor()); + ((TextView) header.findViewById(R.id.settings_notifications_title)) + .setTextColor(style.getTextPrimaryColor()); + ((TextView) header.findViewById(R.id.add_member_title)) + .setTextColor(style.getTextPrimaryColor()); + ((TextView) header.findViewById(R.id.leave_channel_title)) + .setTextColor(style.getTextDangerColor()); + + if (groupVM.getGroupType() == GroupType.CHANNEL) { + ((TextView) header.findViewById(R.id.leave_channel_title)) + .setText(R.string.group_leave_channel); + } else { + ((TextView) header.findViewById(R.id.leave_channel_title)) + .setText(R.string.group_leave); + } + // header.findViewById(R.id.after_about_divider).setBackgroundColor(style.getBackyardBackgroundColor()); + header.findViewById(R.id.after_settings_divider).setBackgroundColor(style.getBackyardBackgroundColor()); // Avatar - bind(avatarView, groupVM.getAvatar()); + // bind(avatarView, groupVM.getAvatar()); + avatarView.bind(groupVM.getAvatar().get(), groupVM.getName().get(), groupVM.getId()); avatarView.setOnClickListener(view -> { startActivity(ViewAvatarActivity.viewGroupAvatar(chatId, getActivity())); }); @@ -144,16 +167,11 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa bind(title, groupVM.getName()); // Owned by - bind(groupVM.getOwnerId(), ownerId -> { - if (ownerId != 0) { - if (ownerId == myUid()) { - createdBy.setText(R.string.group_created_by_you); - } else { - String ownerName = users().get(ownerId).getName().get(); - createdBy.setText(getString(R.string.group_created_by).replace("{0}", ownerName)); - } + bind(groupVM.getMembersCount(), val -> { + if (val != null) { + memberCount.setText(messenger().getFormatter().formatGroupMembers(val)); } else { - createdBy.setText(""); + memberCount.setText(""); } }); @@ -189,13 +207,48 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa isNotificationsEnabled.setChecked(!isNotificationsEnabled.isChecked()); }); + // Add Member + + addMemberCont.setOnClickListener(view -> { + startActivity(new Intent(getActivity(), AddMemberActivity.class) + .putExtra("GROUP_ID", chatId)); + }); + + bind(groupVM.getIsCanInviteMembers(), (canInvite) -> { + if (canInvite) { + addMemberCont.setVisibility(View.VISIBLE); + } else { + addMemberCont.setVisibility(View.GONE); + } + }); + + // Leave + + leaveCont.setOnClickListener(view1 -> { + leaveGroup(); + }); + // Hide Leave button if we can view members + // In this case menu will have such button + bind(groupVM.getIsCanViewMembers(), (canView) -> { + if (canView) { + leaveCont.setVisibility(View.GONE); + } else { + leaveCont.setVisibility(View.VISIBLE); + } + // Invalidate options menu for menu recreation + getActivity().invalidateOptionsMenu(); + }); + + // Members - bind(groupVM.getMembersCount(), val -> { - if (val != null) { - memberCount.setText(val + ""); + bind(groupVM.getIsCanViewMembers(), canViewMembers -> { + if (canViewMembers) { + header.findViewById(R.id.after_settings_divider).setVisibility(View.VISIBLE); + // header.findViewById(R.id.membersHeader).setVisibility(View.VISIBLE); } else { - memberCount.setText(""); + header.findViewById(R.id.after_settings_divider).setVisibility(View.GONE); + // header.findViewById(R.id.membersHeader).setVisibility(View.GONE); } }); @@ -206,24 +259,24 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa // Footer // - // View - View footer = inflater.inflate(R.layout.fragment_group_add, listView, false); - TextView name = (TextView) footer.findViewById(R.id.name); - TintImageView addIcon = (TintImageView) footer.findViewById(R.id.add_icon); - - // Style - footer.findViewById(R.id.bottom_divider).setBackgroundColor(style.getBackyardBackgroundColor()); - name.setTextColor(style.getActionAddContactColor()); - name.setTypeface(Fonts.medium()); - addIcon.setTint(style.getGroupActionAddIconColor()); - addIcon.setTint(style.getActionAddContactColor()); - - footer.findViewById(R.id.addUser).setOnClickListener(v -> { - startActivity(new Intent(getActivity(), AddMemberActivity.class) - .putExtra("GROUP_ID", chatId)); - }); - - listView.addFooterView(footer, null, false); +// // View +// View footer = inflater.inflate(R.layout.fragment_group_add, listView, false); +// TextView name = (TextView) footer.findViewById(R.id.name); +// TintImageView addIcon = (TintImageView) footer.findViewById(R.id.add_icon); +// +// // Style +// footer.findViewById(R.id.bottom_divider).setBackgroundColor(style.getBackyardBackgroundColor()); +// name.setTextColor(style.getActionAddContactColor()); +// name.setTypeface(Fonts.medium()); +// addIcon.setTint(style.getGroupActionAddIconColor()); +// addIcon.setTint(style.getActionAddContactColor()); +// +// footer.findViewById(R.id.addUser).setOnClickListener(v -> { +// startActivity(new Intent(getActivity(), AddMemberActivity.class) +// .putExtra("GROUP_ID", chatId)); +// }); +// +// listView.addFooterView(footer, null, false); // @@ -364,7 +417,7 @@ public void onError(Exception e) { public void updateBar(int offset) { - avatarView.setOffset(offset); + // avatarView.setOffset(offset); int baseColor = getResources().getColor(R.color.primary); ActorStyle style = ActorSDK.sharedActor().style; @@ -393,25 +446,23 @@ public void onCreateOptionsMenu(Menu menu, MenuInflater menuInflater) { super.onCreateOptionsMenu(menu, menuInflater); if (groupVM.isMember().get()) { menuInflater.inflate(R.menu.group_info, menu); + if (!groupVM.getIsCanViewMembers().get()) { + menu.findItem(R.id.leaveGroup).setVisible(false); + } else { + if (groupVM.getGroupType() == GroupType.CHANNEL) { + menu.findItem(R.id.leaveGroup).setTitle(R.string.group_leave_channel); + } else { + menu.findItem(R.id.leaveGroup).setTitle(R.string.group_leave); + } + } } } @Override public boolean onOptionsItemSelected(MenuItem item) { if (item.getItemId() == R.id.leaveGroup) { - new AlertDialog.Builder(getActivity()) - .setMessage(getString(R.string.alert_leave_group_message).replace("%1$s", - groupVM.getName().get())) - .setPositiveButton(R.string.alert_leave_group_yes, (dialog2, which) -> { - execute(messenger().leaveGroup(chatId)); - }) - .setNegativeButton(R.string.dialog_cancel, null) - .show() - .setCanceledOnTouchOutside(true); + leaveGroup(); return true; - } else if (item.getItemId() == R.id.addMember) { - startActivity(new Intent(getActivity(), AddMemberActivity.class) - .putExtra("GROUP_ID", chatId)); } else if (item.getItemId() == R.id.editTitle) { startActivity(Intents.editGroupTitle(chatId, getActivity())); } else if (item.getItemId() == R.id.changePhoto) { @@ -420,6 +471,18 @@ public boolean onOptionsItemSelected(MenuItem item) { return super.onOptionsItemSelected(item); } + private void leaveGroup() { + new AlertDialog.Builder(getActivity()) + .setMessage(getString(R.string.alert_leave_group_message).replace("%1$s", + groupVM.getName().get())) + .setPositiveButton(R.string.alert_leave_group_yes, (dialog2, which) -> { + execute(messenger().leaveGroup(chatId)); + }) + .setNegativeButton(R.string.dialog_cancel, null) + .show() + .setCanceledOnTouchOutside(true); + } + @Override public void onDestroyView() { super.onDestroyView(); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/view/MembersAdapter.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/view/MembersAdapter.java index 25718da36c..a1180322ae 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/view/MembersAdapter.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/view/MembersAdapter.java @@ -86,7 +86,7 @@ public View init(GroupMember data, ViewGroup viewGroup, Context context) { online = (TextView) res.findViewById(R.id.online); ((TextView) admin).setTextColor(ActorSDK.sharedActor().style.getGroupAdminColor()); ((TextView) res.findViewById(R.id.name)).setTextColor(ActorSDK.sharedActor().style.getTextPrimaryColor()); - res.findViewById(R.id.divider).setBackgroundColor(ActorSDK.sharedActor().style.getDividerColor()); + // res.findViewById(R.id.divider).setBackgroundColor(ActorSDK.sharedActor().style.getDividerColor()); return res; } @@ -100,7 +100,6 @@ public void bind(GroupMember data, int position, Context context) { userName.setText(user.getName().get()); - if (data.isAdministrator()) { admin.setVisibility(View.VISIBLE); } else { diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/profile/ProfileFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/profile/ProfileFragment.java index cba7c1f39c..43ff205c73 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/profile/ProfileFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/profile/ProfileFragment.java @@ -211,8 +211,32 @@ public View onCreateView(final LayoutInflater inflater, ViewGroup container, Bun // final LinearLayout contactsContainer = (LinearLayout) res.findViewById(R.id.contactsContainer); + boolean isFirstContact = true; + // + // About + // + + bind(user.getAbout(), new ValueChangedListener() { + private View userAboutRecord; + + @Override + public void onChanged(final String newUserAbout, Value valueModel) { + if (newUserAbout != null && newUserAbout.length() > 0) { + if (userAboutRecord == null) { + userAboutRecord = buildRecordBig(newUserAbout, + R.drawable.ic_info_outline_black_24dp, + true, + true, + inflater, contactsContainer); + } else { + ((TextView) userAboutRecord.findViewById(R.id.value)).setText(newUserAbout); + } + } + } + }); + // // Phones // @@ -305,7 +329,7 @@ public View onCreateView(final LayoutInflater inflater, ViewGroup container, Bun userEmail.getEmail(), R.drawable.ic_import_contacts_black_24dp, isFirstContact, - userName == null && i == emails.size() - 1, + i == emails.size() - 1, inflater, contactsContainer); view.setOnClickListener(v -> { @@ -373,29 +397,6 @@ public void onChanged(final String newUserName, Value valueModel) { } }); - // - // About - // - - bind(user.getAbout(), new ValueChangedListener() { - private View userAboutRecord; - - @Override - public void onChanged(final String newUserAbout, Value valueModel) { - if (newUserAbout != null && newUserAbout.length() > 0) { - if (userAboutRecord == null) { - userAboutRecord = buildRecordBig(newUserAbout, - R.drawable.ic_info_outline_black_24dp, - true, - true, - inflater, contactsContainer); - } else { - ((TextView) userAboutRecord.findViewById(R.id.value)).setText(newUserAbout); - } - } - } - }); - // // Settings diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-hdpi/ic_info_outline_black_18dp.png b/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-hdpi/ic_info_outline_black_18dp.png new file mode 100644 index 0000000000000000000000000000000000000000..839b2e861f9fffe5410e5c5f4b37b7c4c14773eb GIT binary patch literal 475 zcmV<10VMv3P)%eRHk@_)h}Gz`vL RP^$m{002ovPDHLkV1my=)nWhu literal 0 HcmV?d00001 diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-hdpi/ic_menu_black_18dp.png b/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-hdpi/ic_menu_black_18dp.png new file mode 100644 index 0000000000000000000000000000000000000000..86755db4359818833c4cef09d15060ca1ce29845 GIT binary patch literal 122 zcmeAS@N?(olHy`uVBq!ia0vp^(jd&i1|)m0d0oneEP)LkDv^=Km}sfcg^URuMWuNXLVLyp zeW%XGB@#j@V^+f#1((fU&?ATA&qPx&xv>@ZWXv3%yG`9ai$XQC@Yz(2JxQ5KC$d>` s>vPsGy0n?>UqI)wMI3P;YV6PL15(CFPVX_4kN^Mx07*qoM6N<$f)B=dEC2ui literal 0 HcmV?d00001 diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-mdpi/ic_menu_black_18dp.png b/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-mdpi/ic_menu_black_18dp.png new file mode 100644 index 0000000000000000000000000000000000000000..cc9051c75f42a1b23de5612118d211284af1a0e8 GIT binary patch literal 88 zcmeAS@N?(olHy`uVBq!ia0vp^LLkh+1|-AI^@Rf|c~2L|kcwN$2@B_5b~1i;i z6BH(k@T~xD2CyXjhNS7yK2sHZfQmz|J+MCI+dM0*v?bLf4H>Q8IoapVk8BfSE5fe- zME2?V5od&0h|r-T*=OoUq}7!ZimQgqwLIBp?MK`ZVoE5kn$)hP$(rp+IiwRZ9f!mr z(@AO_hr~(UC&YGy4t2YG_sB3Y&v#931s))@5Mk-&#!Zcx8J>4OH z%!HOW&m@HF>nvwnybG)MsOZ4scV>|k1|%Oc%$NbB^gLZ0Ln>~)y>gI~fq{d0N{!5HS*yj*U2*kxmDiG2A0Qz3&2T-sNphzI>gCL0DtD<1$ zqn4)hkiDvbMx8@P*v2#7J9B6D&eA>bd%3&woAbN(%$YNDgh(v0#7fof0XhI#Kn^fT z$k8+Wt-4giv;(FA+kjty-vPk?YyqYKZN7=Q3wRCK3n3&3yMSRpdEBC&1CA^RlJN!5 zABUhiz_J5DGV*}?#S)bUd<$@BA20(L1hfEBfC@s2o?!x+1snu$odBM=7S#*5P&jY} zm^X9+U)I7c3FjQp<5JX>V&WA*y@fd%0IP~QE?tOAE4~!I$u|yO;>a!Yb=gc5+?x7M z`<5$v3^--xJ+_rlmKCBN6d_SHD#Yb&>{DDg=zOUfKt@81#`sP}SoTG!D7ej6cTHs& zj*KS9D8upDv_g5M$bKRQVQ6a%&r1_wd(1vnAEU5FW}o|LQl-nz%o~QZEn#Sj4A&za zf@y}pw5*AmJPNIw@#~!q!8TL-`Cxab@-OslQ|II>$MTFv=f62-?N@~v*ui}6L63wD zG5)oN8JJ}TwnT8m<{DEawt+AMbIib01V?OUML~6<%r_Qh;3PA!!Xsgo4By0mEzI{& ztThcq+sVVWFyrLGrTeT$!i>{>HM2p_dL-<<=?0CqB@I6CL>QJ-hZ5ID)FL?TfiSKJ zeyc;!#tH^@#gm@<45G{?mRIuAomLWR>Ddq!w1)v)UmdUI8wn*Z+&9;kY$ggF zdnW#NJ!}iw7Y=*QIu#0bJ$5Y!+MzP3On7G4Ai%ryO`&eY%ZJ1bI(jsV60s^evJcQ@ n%LHMJp0(00000NkvXXu0mjfV8V>I literal 0 HcmV?d00001 diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xxhdpi/ic_menu_black_18dp.png b/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xxhdpi/ic_menu_black_18dp.png new file mode 100644 index 0000000000000000000000000000000000000000..e4f5420a1f6e1c469e361b7faa347ecffb0c41f6 GIT binary patch literal 119 zcmeAS@N?(olHy`uVBq!ia0vp^W+2SL1|)l2v+e>ZJ5LwKkcwMxuN~xMVBlajbT2QS z>sa5)BJ&__8&KmrwI?l{-n_3TFf*KXo?#GuF^uIvvzXeMuq~pD3=fhS1$s@@pMJf) QA7~PTr>mdKI;Vst05J0-;Q#;t literal 0 HcmV?d00001 diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_group.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_group.xml index f42d230b31..ce95a72838 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_group.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_group.xml @@ -30,10 +30,4 @@ android:text="@string/group_not_member" android:textSize="21sp" /> - - \ No newline at end of file diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_group_header.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_group_header.xml index 7c5fa84bc2..b3e31ebf6e 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_group_header.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_group_header.xml @@ -10,159 +10,115 @@ android:layout_height="wrap_content" android:orientation="vertical"> - - - + android:layout_height="148dp" + android:layout_gravity="bottom" + android:gravity="center_vertical" + android:paddingTop="44dp"> - + android:layout_width="52dp" + android:layout_height="52dp" + android:layout_gravity="center_vertical" + android:layout_margin="18dp" /> + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:layout_weight="1" + android:orientation="vertical"> + android:textSize="20sp" /> - - + android:gravity="left" + android:textSize="16sp" /> - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + android:gravity="top" + android:minHeight="48dp"> + + - - - + - - - + android:layout_height="wrap_content" + android:layout_marginLeft="72dp" + android:layout_marginTop="4dp" + android:background="@color/picker_list_divider" /> - - - + + + + + + + + + app:src="@drawable/ic_menu_black_18dp" /> + + + + + + + + + + + + + android:layout_height="8dp" + android:layout_marginTop="12dp"> - - - - - - - - + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_group_item.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_group_item.xml index d9b18e2b1b..f13caa7823 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_group_item.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_group_item.xml @@ -29,10 +29,10 @@ + android:orientation="vertical"> + - + + + + + \ No newline at end of file diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_profile.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_profile.xml index dd2a761e8d..1a4dbfb975 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_profile.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_profile.xml @@ -197,7 +197,6 @@ android:id="@+id/blockCont" android:layout_width="match_parent" android:layout_height="48dp" - android:layout_marginBottom="36dp" android:background="@drawable/selector" android:paddingRight="8dp"> @@ -219,7 +218,5 @@ android:text="@string/profile_settings_block" android:textSize="16sp" /> - - \ No newline at end of file diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/menu/group_info.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/menu/group_info.xml index 777674e5c7..b84f7594bc 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/menu/group_info.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/menu/group_info.xml @@ -4,14 +4,7 @@ ~ Copyright (C) 2015 Actor LLC. --> - - - + + android:title="@string/group_leave" /> \ No newline at end of file diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-ru/ui_text.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-ru/ui_text.xml index 645df4f8c5..34df360584 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-ru/ui_text.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-ru/ui_text.xml @@ -334,8 +334,6 @@ Инф. о {0} Исключить {0} из группы - Добавить пользователя - Покинуть группу Изменить название Изменить фото Токен интеграции @@ -344,6 +342,8 @@ Документы Добавить в группу + Покинуть группу + Покинуть канал Вы не участник данной группы diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml index d8c0a8467c..c1d0a4292c 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml @@ -1,5 +1,4 @@ - - @@ -269,6 +268,10 @@ You are not a member of this group + Join + Mute + Unmute + START No description for this bot yet @@ -344,8 +347,6 @@ View {0} Remove {0} from group - Add a member - Leave group Change theme Change photo Integration token @@ -354,6 +355,8 @@ Documents Add a member + Leave group + Leave channel You are not a member of this group diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/presence/PresenceActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/presence/PresenceActor.java index af2ed4317e..e2998d9ebf 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/presence/PresenceActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/presence/PresenceActor.java @@ -15,6 +15,7 @@ import im.actor.core.api.rpc.RequestSubscribeToOnline; import im.actor.core.api.rpc.ResponseVoid; import im.actor.core.entity.Group; +import im.actor.core.entity.GroupType; import im.actor.core.entity.Peer; import im.actor.core.entity.PeerType; import im.actor.core.entity.User; @@ -179,6 +180,7 @@ private void subscribe(Peer peer) { // Log.d(TAG, "subscribe:" + peer); if (peer.getPeerType() == PeerType.PRIVATE) { + // Already subscribed if (uids.contains(peer.getPeerId())) { return; @@ -193,6 +195,7 @@ private void subscribe(Peer peer) { uids.add(user.getUid()); } else if (peer.getPeerType() == PeerType.GROUP) { + // Already subscribed if (gids.contains(peer.getPeerId())) { return; @@ -203,6 +206,11 @@ private void subscribe(Peer peer) { return; } + // Ignore subscription to channels + if (group.getGroupType() == GroupType.CHANNEL) { + return; + } + // Subscribing to group online sates gids.add(peer.getPeerId()); diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java index cb8626a19a..d2c0d12d62 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java @@ -57,6 +57,13 @@ public class GroupVM extends BaseValueModel { @NotNull @Property("nonatomic, readonly") private ValueModel> members; + @NotNull + @Property("nonatomic, readonly") + private BooleanValueModel isCanViewMembers; + @NotNull + @Property("nonatomic, readonly") + private BooleanValueModel isCanInviteMembers; + @Property("nonatomic, readonly") private IntValueModel ownerId; @@ -89,6 +96,8 @@ public GroupVM(@NotNull Group rawObj) { this.membersCount = new IntValueModel("group." + groupId + ".membersCount", rawObj.getMembersCount()); this.isCanWriteMessage = new BooleanValueModel("group." + groupId + ".can_write", rawObj.isCanWrite()); + this.isCanViewMembers = new BooleanValueModel("group." + groupId + ".can_view_members", rawObj.isCanViewMembers()); + this.isCanInviteMembers = new BooleanValueModel("group." + groupId + ".can_invite_members", rawObj.isCanInviteMembers()); this.ownerId = new IntValueModel("group." + groupId + ".membersCount", rawObj.getOwnerId()); this.members = new ValueModel<>("group." + groupId + ".members", new HashSet<>(rawObj.getMembers())); this.presence = new ValueModel<>("group." + groupId + ".presence", 0); @@ -172,6 +181,28 @@ public BooleanValueModel getIsCanWriteMessage() { return isCanWriteMessage; } + /** + * Can current user view members of a group + * + * @return can view members model + */ + @NotNull + @ObjectiveCName("getIsCanViewMembersModel") + public BooleanValueModel getIsCanViewMembers() { + return isCanViewMembers; + } + + /** + * Can current user invite members to a group + * + * @return can invite members model + */ + @NotNull + @ObjectiveCName("getIsCanInviteMembersModel") + public BooleanValueModel getIsCanInviteMembers() { + return isCanInviteMembers; + } + /** * Get Group owner user id model * @@ -207,6 +238,7 @@ public ValueModel getPresence() { @Override protected void updateValues(@NotNull Group rawObj) { boolean isChanged = name.change(rawObj.getTitle()); + isChanged |= avatar.change(rawObj.getAvatar()); isChanged |= membersCount.change(rawObj.getMembersCount()); isChanged |= isMember.change(rawObj.isMember()); @@ -216,6 +248,7 @@ protected void updateValues(@NotNull Group rawObj) { isChanged |= about.change(rawObj.getAbout()); isChanged |= members.change(new HashSet<>(rawObj.getMembers())); isChanged |= ownerId.change(rawObj.getOwnerId()); + isChanged |= isCanViewMembers.change(rawObj.isCanViewMembers()); if (isChanged) { notifyChange(); From 762f17d9643eed55754d3abf46ccbe6b44d00299 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Fri, 15 Jul 2016 15:23:09 +0300 Subject: [PATCH 035/414] feat(scheme): Updated permissions in groups --- actor-sdk/sdk-api/actor.json | 560 +++++++++++++++++- .../models/im/actor/api/scheme.mps | 476 ++++++++++++++- 2 files changed, 1030 insertions(+), 6 deletions(-) diff --git a/actor-sdk/sdk-api/actor.json b/actor-sdk/sdk-api/actor.json index 46457c3d4c..68e4ac4d01 100644 --- a/actor-sdk/sdk-api/actor.json +++ b/actor-sdk/sdk-api/actor.json @@ -8146,7 +8146,7 @@ "type": "reference", "argument": "ownerUid", "category": "full", - "description": " Group owner" + "description": " Optional group owner" }, { "type": "reference", @@ -8189,8 +8189,45 @@ "argument": "isSharedHistory", "category": "full", "description": " Is history shared among all users. Default is false." + }, + { + "type": "reference", + "argument": "canEditGroupInfo", + "category": "full", + "description": " If current user can edit group info. Default is true." + }, + { + "type": "reference", + "argument": "shortName", + "category": "full", + "description": " Group's short name" + }, + { + "type": "reference", + "argument": "canEditShortName", + "category": "full", + "description": " If not set only owner can edit group's short name" + }, + { + "type": "reference", + "argument": "canEditAdminList", + "category": "full", + "description": " If not set only owner can edit admin list" + }, + { + "type": "reference", + "argument": "canViewAdminList", + "category": "full", + "description": " If not set only owner and admins can view admin list" + }, + { + "type": "reference", + "argument": "canEditAdminSettings", + "category": "full", + "description": " If not set only owner can edit admin settings" } ], + "expandable": "true", "attributes": [ { "type": { @@ -8210,8 +8247,11 @@ }, { "type": { - "type": "alias", - "childType": "userId" + "type": "opt", + "childType": { + "type": "alias", + "childType": "userId" + } }, "id": 5, "name": "ownerUid" @@ -8285,6 +8325,54 @@ }, "id": 10, "name": "isSharedHistory" + }, + { + "type": { + "type": "opt", + "childType": "bool" + }, + "id": 13, + "name": "canEditGroupInfo" + }, + { + "type": { + "type": "opt", + "childType": "string" + }, + "id": 14, + "name": "shortName" + }, + { + "type": { + "type": "opt", + "childType": "bool" + }, + "id": 15, + "name": "canEditShortName" + }, + { + "type": { + "type": "opt", + "childType": "bool" + }, + "id": 16, + "name": "canEditAdminList" + }, + { + "type": { + "type": "opt", + "childType": "bool" + }, + "id": 17, + "name": "canViewAdminList" + }, + { + "type": { + "type": "opt", + "childType": "bool" + }, + "id": 18, + "name": "canEditAdminSettings" } ] } @@ -8683,6 +8771,46 @@ ] } }, + { + "type": "update", + "content": { + "name": "GroupShortNameChanged", + "header": 2628, + "doc": [ + "Group's short name changed", + { + "type": "reference", + "argument": "groupId", + "category": "full", + "description": " Group Id" + }, + { + "type": "reference", + "argument": "shortName", + "category": "full", + "description": " Group short name" + } + ], + "attributes": [ + { + "type": { + "type": "alias", + "childType": "groupId" + }, + "id": 1, + "name": "groupId" + }, + { + "type": { + "type": "opt", + "childType": "string" + }, + "id": 2, + "name": "shortName" + } + ] + } + }, { "type": "update", "content": { @@ -8860,6 +8988,191 @@ ] } }, + { + "type": "update", + "content": { + "name": "GroupCanEditInfoChanged", + "header": 2631, + "doc": [ + "Update about can edit changed", + { + "type": "reference", + "argument": "groupId", + "category": "full", + "description": " Group Id" + }, + { + "type": "reference", + "argument": "canEditGroup", + "category": "full", + "description": " Can edit group info" + } + ], + "attributes": [ + { + "type": { + "type": "alias", + "childType": "groupId" + }, + "id": 1, + "name": "groupId" + }, + { + "type": "bool", + "id": 2, + "name": "canEditGroup" + } + ] + } + }, + { + "type": "update", + "content": { + "name": "GroupCanEditUsernameChanged", + "header": 2632, + "doc": [ + "Update about can edit username changed", + { + "type": "reference", + "argument": "groupId", + "category": "full", + "description": " Group Id" + }, + { + "type": "reference", + "argument": "canEditUsername", + "category": "full", + "description": " Can edit username" + } + ], + "attributes": [ + { + "type": { + "type": "alias", + "childType": "groupId" + }, + "id": 1, + "name": "groupId" + }, + { + "type": "bool", + "id": 2, + "name": "canEditUsername" + } + ] + } + }, + { + "type": "update", + "content": { + "name": "GroupCanEditAdminsChanged", + "header": 2633, + "doc": [ + "Update about can edit admins changed", + { + "type": "reference", + "argument": "groupId", + "category": "full", + "description": " Group Id" + }, + { + "type": "reference", + "argument": "canAssignAdmins", + "category": "hidden", + "description": " Can assign admins" + } + ], + "attributes": [ + { + "type": { + "type": "alias", + "childType": "groupId" + }, + "id": 1, + "name": "groupId" + }, + { + "type": "bool", + "id": 2, + "name": "canAssignAdmins" + } + ] + } + }, + { + "type": "update", + "content": { + "name": "GroupCanViewAdminsChanged", + "header": 2640, + "doc": [ + "Update about view admings changed", + { + "type": "reference", + "argument": "groupId", + "category": "full", + "description": " Group Id" + }, + { + "type": "reference", + "argument": "canViewAdmins", + "category": "hidden", + "description": " Can view admins" + } + ], + "attributes": [ + { + "type": { + "type": "alias", + "childType": "groupId" + }, + "id": 1, + "name": "groupId" + }, + { + "type": "bool", + "id": 2, + "name": "canViewAdmins" + } + ] + } + }, + { + "type": "update", + "content": { + "name": "GroupCanEditAdminSettingsChanged", + "header": 2641, + "doc": [ + "Update about edit admin settings changed", + { + "type": "reference", + "argument": "groupId", + "category": "full", + "description": " Group Id" + }, + { + "type": "reference", + "argument": "canEditAdminSettings", + "category": "full", + "description": " Can edit admin settings" + } + ], + "attributes": [ + { + "type": { + "type": "alias", + "childType": "groupId" + }, + "id": 1, + "name": "groupId" + }, + { + "type": "bool", + "id": 2, + "name": "canEditAdminSettings" + } + ] + } + }, { "type": "comment", "content": " " @@ -9577,6 +9890,50 @@ ] } }, + { + "type": "rpc", + "content": { + "name": "EditGroupShortName", + "header": 2793, + "response": { + "type": "reference", + "name": "Seq" + }, + "doc": [ + "Edit Group Short Name", + { + "type": "reference", + "argument": "groupPeer", + "category": "full", + "description": "Group's peer" + }, + { + "type": "reference", + "argument": "shortName", + "category": "full", + "description": "New group's short name" + } + ], + "attributes": [ + { + "type": { + "type": "struct", + "childType": "GroupOutPeer" + }, + "id": 1, + "name": "groupPeer" + }, + { + "type": { + "type": "opt", + "childType": "string" + }, + "id": 2, + "name": "shortName" + } + ] + } + }, { "type": "rpc", "content": { @@ -9938,6 +10295,10 @@ ] } }, + { + "type": "comment", + "content": "Administration" + }, { "type": "rpc", "content": { @@ -9982,6 +10343,50 @@ ] } }, + { + "type": "rpc", + "content": { + "name": "DismissUserAdmin", + "header": 2791, + "response": { + "type": "reference", + "name": "Seq" + }, + "doc": [ + "Dismissing user admin", + { + "type": "reference", + "argument": "groupPeer", + "category": "full", + "description": "Group's peer" + }, + { + "type": "reference", + "argument": "userPeer", + "category": "full", + "description": "User's peer" + } + ], + "attributes": [ + { + "type": { + "type": "struct", + "childType": "GroupOutPeer" + }, + "id": 1, + "name": "groupPeer" + }, + { + "type": { + "type": "struct", + "childType": "UserOutPeer" + }, + "id": 2, + "name": "userPeer" + } + ] + } + }, { "type": "rpc", "content": { @@ -10026,6 +10431,155 @@ ] } }, + { + "type": "struct", + "content": { + "name": "AdminSettings", + "doc": [ + "Admin Settings", + { + "type": "reference", + "argument": "showAdminsToMembers", + "category": "full", + "description": " Show admins in member list" + }, + { + "type": "reference", + "argument": "canMembersInvite", + "category": "full", + "description": " Can members of a group invite people" + }, + { + "type": "reference", + "argument": "canMembersEditGroupInfo", + "category": "full", + "description": " Can members edit group info" + }, + { + "type": "reference", + "argument": "canAdminsEditGroupInfo", + "category": "full", + "description": " Can admins edit group info" + } + ], + "expandable": "true", + "attributes": [ + { + "type": "bool", + "id": 1, + "name": "showAdminsToMembers" + }, + { + "type": "bool", + "id": 2, + "name": "canMembersInvite" + }, + { + "type": "bool", + "id": 3, + "name": "canMembersEditGroupInfo" + }, + { + "type": "bool", + "id": 4, + "name": "canAdminsEditGroupInfo" + } + ] + } + }, + { + "type": "rpc", + "content": { + "name": "LoadAdminSettings", + "header": 2790, + "response": { + "type": "anonymous", + "header": 2794, + "doc": [ + "Loaded settings", + { + "type": "reference", + "argument": "settings", + "category": "full", + "description": " Current group admin settings" + } + ], + "attributes": [ + { + "type": { + "type": "struct", + "childType": "AdminSettings" + }, + "id": 1, + "name": "settings" + } + ] + }, + "doc": [ + "Loading administration settings", + { + "type": "reference", + "argument": "groupPeer", + "category": "full", + "description": "Group's peer" + } + ], + "attributes": [ + { + "type": { + "type": "struct", + "childType": "GroupOutPeer" + }, + "id": 1, + "name": "groupPeer" + } + ] + } + }, + { + "type": "rpc", + "content": { + "name": "SaveAdminSettings", + "header": 2792, + "response": { + "type": "reference", + "name": "Void" + }, + "doc": [ + "Save administartion settings", + { + "type": "reference", + "argument": "groupPeer", + "category": "full", + "description": "Group's Peer" + }, + { + "type": "reference", + "argument": "settings", + "category": "full", + "description": "Group's settings" + } + ], + "attributes": [ + { + "type": { + "type": "struct", + "childType": "GroupOutPeer" + }, + "id": 1, + "name": "groupPeer" + }, + { + "type": { + "type": "struct", + "childType": "AdminSettings" + }, + "id": 2, + "name": "settings" + } + ] + } + }, { "type": "comment", "content": "Invite" diff --git a/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps b/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps index 04be18fc4a..e9ba693420 100644 --- a/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps +++ b/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps @@ -7130,6 +7130,7 @@ + @@ -7147,8 +7148,10 @@ - - + + + + @@ -7211,6 +7214,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -7225,7 +7270,7 @@ - + @@ -7264,6 +7309,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -7586,6 +7661,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -7733,6 +7841,159 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -8331,6 +8592,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -8639,6 +8936,9 @@ + + + @@ -8685,6 +8985,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -8721,6 +9067,130 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 094db53e45eeab878d2502162943d36df2473eb2 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Fri, 15 Jul 2016 15:23:09 +0300 Subject: [PATCH 036/414] feat(scheme): Updated permissions in groups --- actor-sdk/sdk-api/actor.json | 560 +++++++++++++++++- .../models/im/actor/api/scheme.mps | 476 ++++++++++++++- 2 files changed, 1030 insertions(+), 6 deletions(-) diff --git a/actor-sdk/sdk-api/actor.json b/actor-sdk/sdk-api/actor.json index 4c358dbec1..7219e2b553 100644 --- a/actor-sdk/sdk-api/actor.json +++ b/actor-sdk/sdk-api/actor.json @@ -8140,7 +8140,7 @@ "type": "reference", "argument": "ownerUid", "category": "full", - "description": " Group owner" + "description": " Optional group owner" }, { "type": "reference", @@ -8183,8 +8183,45 @@ "argument": "isSharedHistory", "category": "full", "description": " Is history shared among all users. Default is false." + }, + { + "type": "reference", + "argument": "canEditGroupInfo", + "category": "full", + "description": " If current user can edit group info. Default is true." + }, + { + "type": "reference", + "argument": "shortName", + "category": "full", + "description": " Group's short name" + }, + { + "type": "reference", + "argument": "canEditShortName", + "category": "full", + "description": " If not set only owner can edit group's short name" + }, + { + "type": "reference", + "argument": "canEditAdminList", + "category": "full", + "description": " If not set only owner can edit admin list" + }, + { + "type": "reference", + "argument": "canViewAdminList", + "category": "full", + "description": " If not set only owner and admins can view admin list" + }, + { + "type": "reference", + "argument": "canEditAdminSettings", + "category": "full", + "description": " If not set only owner can edit admin settings" } ], + "expandable": "true", "attributes": [ { "type": { @@ -8204,8 +8241,11 @@ }, { "type": { - "type": "alias", - "childType": "userId" + "type": "opt", + "childType": { + "type": "alias", + "childType": "userId" + } }, "id": 5, "name": "ownerUid" @@ -8279,6 +8319,54 @@ }, "id": 10, "name": "isSharedHistory" + }, + { + "type": { + "type": "opt", + "childType": "bool" + }, + "id": 13, + "name": "canEditGroupInfo" + }, + { + "type": { + "type": "opt", + "childType": "string" + }, + "id": 14, + "name": "shortName" + }, + { + "type": { + "type": "opt", + "childType": "bool" + }, + "id": 15, + "name": "canEditShortName" + }, + { + "type": { + "type": "opt", + "childType": "bool" + }, + "id": 16, + "name": "canEditAdminList" + }, + { + "type": { + "type": "opt", + "childType": "bool" + }, + "id": 17, + "name": "canViewAdminList" + }, + { + "type": { + "type": "opt", + "childType": "bool" + }, + "id": 18, + "name": "canEditAdminSettings" } ] } @@ -8677,6 +8765,46 @@ ] } }, + { + "type": "update", + "content": { + "name": "GroupShortNameChanged", + "header": 2628, + "doc": [ + "Group's short name changed", + { + "type": "reference", + "argument": "groupId", + "category": "full", + "description": " Group Id" + }, + { + "type": "reference", + "argument": "shortName", + "category": "full", + "description": " Group short name" + } + ], + "attributes": [ + { + "type": { + "type": "alias", + "childType": "groupId" + }, + "id": 1, + "name": "groupId" + }, + { + "type": { + "type": "opt", + "childType": "string" + }, + "id": 2, + "name": "shortName" + } + ] + } + }, { "type": "update", "content": { @@ -8854,6 +8982,191 @@ ] } }, + { + "type": "update", + "content": { + "name": "GroupCanEditInfoChanged", + "header": 2631, + "doc": [ + "Update about can edit changed", + { + "type": "reference", + "argument": "groupId", + "category": "full", + "description": " Group Id" + }, + { + "type": "reference", + "argument": "canEditGroup", + "category": "full", + "description": " Can edit group info" + } + ], + "attributes": [ + { + "type": { + "type": "alias", + "childType": "groupId" + }, + "id": 1, + "name": "groupId" + }, + { + "type": "bool", + "id": 2, + "name": "canEditGroup" + } + ] + } + }, + { + "type": "update", + "content": { + "name": "GroupCanEditUsernameChanged", + "header": 2632, + "doc": [ + "Update about can edit username changed", + { + "type": "reference", + "argument": "groupId", + "category": "full", + "description": " Group Id" + }, + { + "type": "reference", + "argument": "canEditUsername", + "category": "full", + "description": " Can edit username" + } + ], + "attributes": [ + { + "type": { + "type": "alias", + "childType": "groupId" + }, + "id": 1, + "name": "groupId" + }, + { + "type": "bool", + "id": 2, + "name": "canEditUsername" + } + ] + } + }, + { + "type": "update", + "content": { + "name": "GroupCanEditAdminsChanged", + "header": 2633, + "doc": [ + "Update about can edit admins changed", + { + "type": "reference", + "argument": "groupId", + "category": "full", + "description": " Group Id" + }, + { + "type": "reference", + "argument": "canAssignAdmins", + "category": "hidden", + "description": " Can assign admins" + } + ], + "attributes": [ + { + "type": { + "type": "alias", + "childType": "groupId" + }, + "id": 1, + "name": "groupId" + }, + { + "type": "bool", + "id": 2, + "name": "canAssignAdmins" + } + ] + } + }, + { + "type": "update", + "content": { + "name": "GroupCanViewAdminsChanged", + "header": 2640, + "doc": [ + "Update about view admings changed", + { + "type": "reference", + "argument": "groupId", + "category": "full", + "description": " Group Id" + }, + { + "type": "reference", + "argument": "canViewAdmins", + "category": "hidden", + "description": " Can view admins" + } + ], + "attributes": [ + { + "type": { + "type": "alias", + "childType": "groupId" + }, + "id": 1, + "name": "groupId" + }, + { + "type": "bool", + "id": 2, + "name": "canViewAdmins" + } + ] + } + }, + { + "type": "update", + "content": { + "name": "GroupCanEditAdminSettingsChanged", + "header": 2641, + "doc": [ + "Update about edit admin settings changed", + { + "type": "reference", + "argument": "groupId", + "category": "full", + "description": " Group Id" + }, + { + "type": "reference", + "argument": "canEditAdminSettings", + "category": "full", + "description": " Can edit admin settings" + } + ], + "attributes": [ + { + "type": { + "type": "alias", + "childType": "groupId" + }, + "id": 1, + "name": "groupId" + }, + { + "type": "bool", + "id": 2, + "name": "canEditAdminSettings" + } + ] + } + }, { "type": "comment", "content": " " @@ -9571,6 +9884,50 @@ ] } }, + { + "type": "rpc", + "content": { + "name": "EditGroupShortName", + "header": 2793, + "response": { + "type": "reference", + "name": "Seq" + }, + "doc": [ + "Edit Group Short Name", + { + "type": "reference", + "argument": "groupPeer", + "category": "full", + "description": "Group's peer" + }, + { + "type": "reference", + "argument": "shortName", + "category": "full", + "description": "New group's short name" + } + ], + "attributes": [ + { + "type": { + "type": "struct", + "childType": "GroupOutPeer" + }, + "id": 1, + "name": "groupPeer" + }, + { + "type": { + "type": "opt", + "childType": "string" + }, + "id": 2, + "name": "shortName" + } + ] + } + }, { "type": "rpc", "content": { @@ -9932,6 +10289,10 @@ ] } }, + { + "type": "comment", + "content": "Administration" + }, { "type": "rpc", "content": { @@ -9976,6 +10337,50 @@ ] } }, + { + "type": "rpc", + "content": { + "name": "DismissUserAdmin", + "header": 2791, + "response": { + "type": "reference", + "name": "Seq" + }, + "doc": [ + "Dismissing user admin", + { + "type": "reference", + "argument": "groupPeer", + "category": "full", + "description": "Group's peer" + }, + { + "type": "reference", + "argument": "userPeer", + "category": "full", + "description": "User's peer" + } + ], + "attributes": [ + { + "type": { + "type": "struct", + "childType": "GroupOutPeer" + }, + "id": 1, + "name": "groupPeer" + }, + { + "type": { + "type": "struct", + "childType": "UserOutPeer" + }, + "id": 2, + "name": "userPeer" + } + ] + } + }, { "type": "rpc", "content": { @@ -10020,6 +10425,155 @@ ] } }, + { + "type": "struct", + "content": { + "name": "AdminSettings", + "doc": [ + "Admin Settings", + { + "type": "reference", + "argument": "showAdminsToMembers", + "category": "full", + "description": " Show admins in member list" + }, + { + "type": "reference", + "argument": "canMembersInvite", + "category": "full", + "description": " Can members of a group invite people" + }, + { + "type": "reference", + "argument": "canMembersEditGroupInfo", + "category": "full", + "description": " Can members edit group info" + }, + { + "type": "reference", + "argument": "canAdminsEditGroupInfo", + "category": "full", + "description": " Can admins edit group info" + } + ], + "expandable": "true", + "attributes": [ + { + "type": "bool", + "id": 1, + "name": "showAdminsToMembers" + }, + { + "type": "bool", + "id": 2, + "name": "canMembersInvite" + }, + { + "type": "bool", + "id": 3, + "name": "canMembersEditGroupInfo" + }, + { + "type": "bool", + "id": 4, + "name": "canAdminsEditGroupInfo" + } + ] + } + }, + { + "type": "rpc", + "content": { + "name": "LoadAdminSettings", + "header": 2790, + "response": { + "type": "anonymous", + "header": 2794, + "doc": [ + "Loaded settings", + { + "type": "reference", + "argument": "settings", + "category": "full", + "description": " Current group admin settings" + } + ], + "attributes": [ + { + "type": { + "type": "struct", + "childType": "AdminSettings" + }, + "id": 1, + "name": "settings" + } + ] + }, + "doc": [ + "Loading administration settings", + { + "type": "reference", + "argument": "groupPeer", + "category": "full", + "description": "Group's peer" + } + ], + "attributes": [ + { + "type": { + "type": "struct", + "childType": "GroupOutPeer" + }, + "id": 1, + "name": "groupPeer" + } + ] + } + }, + { + "type": "rpc", + "content": { + "name": "SaveAdminSettings", + "header": 2792, + "response": { + "type": "reference", + "name": "Void" + }, + "doc": [ + "Save administartion settings", + { + "type": "reference", + "argument": "groupPeer", + "category": "full", + "description": "Group's Peer" + }, + { + "type": "reference", + "argument": "settings", + "category": "full", + "description": "Group's settings" + } + ], + "attributes": [ + { + "type": { + "type": "struct", + "childType": "GroupOutPeer" + }, + "id": 1, + "name": "groupPeer" + }, + { + "type": { + "type": "struct", + "childType": "AdminSettings" + }, + "id": 2, + "name": "settings" + } + ] + } + }, { "type": "comment", "content": "Invite" diff --git a/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps b/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps index 05a8d5d084..da8e3130f9 100644 --- a/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps +++ b/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps @@ -7130,6 +7130,7 @@ + @@ -7147,8 +7148,10 @@ - - + + + + @@ -7211,6 +7214,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -7225,7 +7270,7 @@ - + @@ -7264,6 +7309,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -7586,6 +7661,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -7733,6 +7841,159 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -8331,6 +8592,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -8639,6 +8936,9 @@ + + + @@ -8685,6 +8985,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -8721,6 +9067,130 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 4106993eb3535b9e89be68c6bfbef79f0135d01c Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Fri, 15 Jul 2016 15:57:35 +0300 Subject: [PATCH 037/414] feat(android+core): Update service messages for channels --- .../messages/MessagesAdapter.java | 11 ++- .../messages/content/ServiceHolder.java | 7 +- .../controllers/dialogs/view/DialogView.java | 4 +- .../im/actor/core/entity/Notification.java | 9 +- .../java/im/actor/core/i18n/I18nEngine.java | 57 ++++++------ .../notifications/NotificationsActor.java | 9 +- .../src/main/resources/AppText.json | 21 +++++ .../src/main/resources/AppText_Ar.json | 21 +++++ .../src/main/resources/AppText_Es.json | 21 +++++ .../src/main/resources/AppText_Fa.json | 21 +++++ .../src/main/resources/AppText_Pt.json | 21 +++++ .../src/main/resources/AppText_Ru.json | 89 ++++++++++++++++++- .../src/main/resources/AppText_Zn.json | 21 +++++ 13 files changed, 276 insertions(+), 36 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/MessagesAdapter.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/MessagesAdapter.java index b88bd3772e..ab40cf2378 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/MessagesAdapter.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/MessagesAdapter.java @@ -7,7 +7,9 @@ import java.util.HashMap; +import im.actor.core.entity.GroupType; import im.actor.core.entity.Message; +import im.actor.core.entity.PeerType; import im.actor.core.entity.content.AbsContent; import im.actor.core.entity.content.AnimationContent; import im.actor.core.entity.content.ContactContent; @@ -42,6 +44,7 @@ import im.actor.sdk.controllers.conversation.messages.content.UnsupportedHolder; import im.actor.sdk.controllers.ActorBinder; +import static im.actor.sdk.util.ActorSDKMessenger.groups; import static im.actor.sdk.util.ActorSDKMessenger.messenger; public class MessagesAdapter extends BindedListAdapter { @@ -53,6 +56,7 @@ public class MessagesAdapter extends BindedListAdapter { private long firstUnread = -1; private long readDate; private long receiveDate; + private boolean isChannel; private HashMap selected = new HashMap<>(); @@ -64,6 +68,11 @@ public MessagesAdapter(final BindedDisplayList displayList, this.context = context; ConversationVM conversationVM = messenger().getConversationVM(messagesFragment.getPeer()); + isChannel = false; + if (messagesFragment.getPeer().getPeerType() == PeerType.GROUP) { + isChannel = groups().get(messagesFragment.getPeer().getPeerId()).getGroupType() == GroupType.CHANNEL; + } + readDate = conversationVM.getReadDate().get(); receiveDate = conversationVM.getReceiveDate().get(); @@ -204,7 +213,7 @@ public TextHolder onNotDelegated() { return ActorSDK.sharedActor().getDelegatedViewHolder(ServiceHolder.class, new ActorSDK.OnDelegateViewHolder() { @Override public ServiceHolder onNotDelegated() { - return new ServiceHolder(MessagesAdapter.this, inflate(R.layout.adapter_dialog_service, viewGroup)); + return new ServiceHolder(MessagesAdapter.this, isChannel, inflate(R.layout.adapter_dialog_service, viewGroup)); } }, MessagesAdapter.this, inflate(R.layout.adapter_dialog_service, viewGroup)); case 2: diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/ServiceHolder.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/ServiceHolder.java index 1dbd25a7fc..c0814485f4 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/ServiceHolder.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/ServiceHolder.java @@ -15,10 +15,13 @@ public class ServiceHolder extends MessageHolder { private TextView messageText; + private boolean isChannel; - public ServiceHolder(MessagesAdapter fragment, View itemView) { + public ServiceHolder(MessagesAdapter fragment, boolean isChannel, View itemView) { super(fragment, itemView, true); + this.isChannel = isChannel; + messageText = (TextView) itemView.findViewById(R.id.serviceMessage); messageText.setTextColor(ActorSDK.sharedActor().style.getConvDatetextColor()); onConfigureViewHolder(); @@ -27,6 +30,6 @@ public ServiceHolder(MessagesAdapter fragment, View itemView) { @Override protected void bindData(Message message, long readDate, long receiveDate, boolean isUpdated, PreprocessedData preprocessedData) { messageText.setText(messenger().getFormatter().formatFullServiceMessage(message.getSenderId(), - (ServiceContent) message.getContent())); + (ServiceContent) message.getContent(), isChannel)); } } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/view/DialogView.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/view/DialogView.java index 155b41089b..9891db0a46 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/view/DialogView.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/view/DialogView.java @@ -399,8 +399,10 @@ public void onDownloaded(FileSystemReference reference) { } if (arg.getSenderId() > 0) { + String contentText = messenger().getFormatter().formatContentText(arg.getSenderId(), - arg.getMessageType(), arg.getText().replace("\n", " "), arg.getRelatedUid()); + arg.getMessageType(), arg.getText().replace("\n", " "), arg.getRelatedUid(), + arg.isChannel()); if (arg.getPeer().getPeerType() == PeerType.GROUP) { if (messenger().getFormatter().isLargeDialogMessage(arg.getMessageType())) { diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Notification.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Notification.java index 0998e1ce07..6663beaa42 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Notification.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Notification.java @@ -10,12 +10,15 @@ public class Notification { @Property("readonly, nonatomic") private Peer peer; @Property("readonly, nonatomic") + private boolean isChannel; + @Property("readonly, nonatomic") private int sender; @Property("readonly, nonatomic") private ContentDescription contentDescription; - public Notification(Peer peer, int sender, ContentDescription contentDescription) { + public Notification(Peer peer, boolean isChannel, int sender, ContentDescription contentDescription) { this.peer = peer; + this.isChannel = isChannel; this.sender = sender; this.contentDescription = contentDescription; } @@ -28,6 +31,10 @@ public int getSender() { return sender; } + public boolean isChannel() { + return isChannel; + } + public ContentDescription getContentDescription() { return contentDescription; } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/i18n/I18nEngine.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/i18n/I18nEngine.java index de1516054d..4d87232228 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/i18n/I18nEngine.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/i18n/I18nEngine.java @@ -225,7 +225,8 @@ public String formatDialogText(Dialog dialog) { return ""; } else { String contentText = formatContentText(dialog.getSenderId(), - dialog.getMessageType(), dialog.getText(), dialog.getRelatedUid()); + dialog.getMessageType(), dialog.getText(), dialog.getRelatedUid(), + dialog.isChannel()); if (dialog.getPeer().getPeerType() == PeerType.GROUP) { if (!isLargeDialogMessage(dialog.getMessageType())) { return formatPerformerName(dialog.getSenderId()) + ": " + contentText; @@ -279,7 +280,8 @@ public String formatNotificationText(Notification pendingNotification) { return formatContentText(pendingNotification.getSender(), pendingNotification.getContentDescription().getContentType(), pendingNotification.getContentDescription().getText(), - pendingNotification.getContentDescription().getRelatedUser()); + pendingNotification.getContentDescription().getRelatedUser(), + pendingNotification.isChannel()); } /** @@ -291,8 +293,12 @@ public String formatNotificationText(Notification pendingNotification) { * @param relatedUid optional related uid * @return formatted content */ - @ObjectiveCName("formatContentTextWithSenderId:withContentType:withText:withRelatedUid:") - public String formatContentText(int senderId, ContentType contentType, String text, int relatedUid) { + @ObjectiveCName("formatContentTextWithSenderId:withContentType:withText:withRelatedUid:withIsChannel") + public String formatContentText(int senderId, ContentType contentType, String text, int relatedUid, + boolean isChannel) { + + String groupKey = isChannel ? "channels" : "groups"; + switch (contentType) { case TEXT: return text; @@ -323,27 +329,27 @@ public String formatContentText(int senderId, ContentType contentType, String te return getTemplateNamed(senderId, "content.service.registered.compact") .replace("{app_name}", getAppName()); case SERVICE_CREATED: - return getTemplateNamed(senderId, "content.service.groups.created"); + return getTemplateNamed(senderId, "content.service." + groupKey + ".created"); case SERVICE_ADD: - return getTemplateNamed(senderId, "content.service.groups.invited") + return getTemplateNamed(senderId, "content.service." + groupKey + ".invited") .replace("{name_added}", getSubjectName(relatedUid)); case SERVICE_LEAVE: - return getTemplateNamed(senderId, "content.service.groups.left"); + return getTemplateNamed(senderId, "content.service." + groupKey + ".left"); case SERVICE_KICK: - return getTemplateNamed(senderId, "content.service.groups.kicked") + return getTemplateNamed(senderId, "content.service." + groupKey + ".kicked") .replace("{name_kicked}", getSubjectName(relatedUid)); case SERVICE_AVATAR: - return getTemplateNamed(senderId, "content.service.groups.avatar_changed"); + return getTemplateNamed(senderId, "content.service." + groupKey + ".avatar_changed"); case SERVICE_AVATAR_REMOVED: - return getTemplateNamed(senderId, "content.service.groups.avatar_removed"); + return getTemplateNamed(senderId, "content.service." + groupKey + ".avatar_removed"); case SERVICE_TITLE: - return getTemplateNamed(senderId, "content.service.groups.title_changed.compact"); + return getTemplateNamed(senderId, "content.service." + groupKey + ".title_changed.compact"); case SERVICE_TOPIC: - return getTemplateNamed(senderId, "content.service.groups.topic_changed.compact"); + return getTemplateNamed(senderId, "content.service." + groupKey + ".topic_changed.compact"); case SERVICE_ABOUT: - return getTemplateNamed(senderId, "content.service.groups.about_changed.compact"); + return getTemplateNamed(senderId, "content.service." + groupKey + ".about_changed.compact"); case SERVICE_JOINED: - return getTemplateNamed(senderId, "content.service.groups.joined"); + return getTemplateNamed(senderId, "content.service." + groupKey + ".joined"); case SERVICE_CALL_ENDED: return get("content.service.calls.ended"); case SERVICE_CALL_MISSED: @@ -364,42 +370,43 @@ public String formatContentText(int senderId, ContentType contentType, String te * @return formatted message */ @ObjectiveCName("formatFullServiceMessageWithSenderId:withContent:") - public String formatFullServiceMessage(int senderId, ServiceContent content) { + public String formatFullServiceMessage(int senderId, ServiceContent content, boolean isChannel) { + String groupKey = isChannel ? "channels" : "groups"; if (content instanceof ServiceUserRegistered) { return getTemplateNamed(senderId, "content.service.registered.full") .replace("{app_name}", getAppName()); } else if (content instanceof ServiceGroupCreated) { - return getTemplateNamed(senderId, "content.service.groups.created"); + return getTemplateNamed(senderId, "content.service." + groupKey + ".created"); } else if (content instanceof ServiceGroupUserInvited) { - return getTemplateNamed(senderId, "content.service.groups.invited") + return getTemplateNamed(senderId, "content.service." + groupKey + ".invited") .replace("{name_added}", getSubjectName(((ServiceGroupUserInvited) content).getAddedUid())); } else if (content instanceof ServiceGroupUserKicked) { - return getTemplateNamed(senderId, "content.service.groups.kicked") + return getTemplateNamed(senderId, "content.service." + groupKey + ".kicked") .replace("{name_kicked}", getSubjectName(((ServiceGroupUserKicked) content).getKickedUid())); } else if (content instanceof ServiceGroupUserLeave) { - return getTemplateNamed(senderId, "content.service.groups.left"); + return getTemplateNamed(senderId, "content.service." + groupKey + ".left"); } else if (content instanceof ServiceGroupTitleChanged) { - return getTemplateNamed(senderId, "content.service.groups.title_changed.full") + return getTemplateNamed(senderId, "content.service." + groupKey + ".title_changed.full") .replace("{title}", ((ServiceGroupTitleChanged) content).getNewTitle()); } else if (content instanceof ServiceGroupTopicChanged) { - return getTemplateNamed(senderId, "content.service.groups.topic_changed.full") + return getTemplateNamed(senderId, "content.service." + groupKey + ".topic_changed.full") .replace("{topic}", ((ServiceGroupTopicChanged) content).getNewTopic()); } else if (content instanceof ServiceGroupAboutChanged) { - return getTemplateNamed(senderId, "content.service.groups.about_changed.full") + return getTemplateNamed(senderId, "content.service." + groupKey + ".about_changed.full") .replace("{about}", ((ServiceGroupAboutChanged) content).getNewAbout()); } else if (content instanceof ServiceGroupAvatarChanged) { if (((ServiceGroupAvatarChanged) content).getNewAvatar() != null) { - return getTemplateNamed(senderId, "content.service.groups.avatar_changed"); + return getTemplateNamed(senderId, "content.service." + groupKey + ".avatar_changed"); } else { - return getTemplateNamed(senderId, "content.service.groups.avatar_removed"); + return getTemplateNamed(senderId, "content.service." + groupKey + ".avatar_removed"); } } else if (content instanceof ServiceGroupUserJoined) { - return getTemplateNamed(senderId, "content.service.groups.joined"); + return getTemplateNamed(senderId, "content.service." + groupKey + ".joined"); } else if (content instanceof ServiceCallEnded) { return get("content.service.calls.ended"); } else if (content instanceof ServiceCallMissed) { diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/notifications/NotificationsActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/notifications/NotificationsActor.java index 20add7552b..95af715229 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/notifications/NotificationsActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/notifications/NotificationsActor.java @@ -12,6 +12,7 @@ import im.actor.core.PlatformType; import im.actor.core.entity.ContentDescription; +import im.actor.core.entity.GroupType; import im.actor.core.entity.Notification; import im.actor.core.entity.Peer; import im.actor.core.entity.PeerType; @@ -31,7 +32,7 @@ /** * Actor that controls all notifications in application - *

+ *

* NotificationsActor keeps all unread messages for showing last unread messages in notifications * Actor also control sound effects playing logic */ @@ -407,7 +408,11 @@ private void performNotificationImp(boolean performUpdate) { // Converting to PendingNotifications List res = new ArrayList<>(); for (PendingNotification p : destNotifications) { - res.add(new Notification(p.getPeer(), p.getSender(), p.getContent())); + boolean isChannel = false; + if (p.getPeer().getPeerType() == PeerType.GROUP) { + isChannel = groups().getValue(p.getPeer().getPeerId()).getGroupType() == GroupType.CHANNEL; + } + res.add(new Notification(p.getPeer(), isChannel, p.getSender(), p.getContent())); } // Performing notifications diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/resources/AppText.json b/actor-sdk/sdk-core/core/core-shared/src/main/resources/AppText.json index 78f9d1a313..53778a8d92 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/resources/AppText.json +++ b/actor-sdk/sdk-core/core/core-shared/src/main/resources/AppText.json @@ -107,6 +107,27 @@ "avatar_changed": "{name} changed the group photo", "avatar_removed": "{name} removed the group photo" }, + "channels": { + "created": "Channel created", + "invited": "{name} invited {name_added}", + "joined": "{name} joined channel", + "kicked": "{name} kicked {name_kicked}", + "left": "{name} left channel", + "title_changed": { + "full": "{name} changed the channel name to \"{title}\"", + "compact": "{name} changed the channel name" + }, + "topic_changed": { + "full": "{name} changed the channel topic to \"{topic}\"", + "compact": "{name} changed the channel topic" + }, + "about_changed": { + "full": "{name} changed the channel about to \"{about}\"", + "compact": "{name} changed the channel about" + }, + "avatar_changed": "{name} changed the channel photo", + "avatar_removed": "{name} removed the channel photo" + }, "calls": { "missed": "Missed call", "ended": "Call ended" diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/resources/AppText_Ar.json b/actor-sdk/sdk-core/core/core-shared/src/main/resources/AppText_Ar.json index 68524a5980..308c7b5142 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/resources/AppText_Ar.json +++ b/actor-sdk/sdk-core/core/core-shared/src/main/resources/AppText_Ar.json @@ -94,6 +94,27 @@ "avatar_changed": "{name} غير صورة المجموعة", "avatar_removed": "{name} حذف صورة المجموعة" }, + "channels": { + "created": " انشأ المجموعة", + "invited": "{name} دعى {name_added}", + "joined": "{name} انصم للمجموعة", + "kicked": "{name} ازال {name_kicked}", + "left": "{name} غادر", + "title_changed": { + "full": "{name} غير الاسم الى \"{title}\"", + "compact": "{name} غير اسم المجموعة الى" + }, + "topic_changed": { + "full": "{name} changed the group topic to \"{topic}\"", + "compact": "{name} changed the group topic" + }, + "about_changed": { + "full": "{name} changed the group about to \"{about}\"", + "compact": "{name} changed the group about" + }, + "avatar_changed": "{name} غير صورة المجموعة", + "avatar_removed": "{name} حذف صورة المجموعة" + }, "calls": { "missed": "Missed call", "ended": "Call ended" diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/resources/AppText_Es.json b/actor-sdk/sdk-core/core/core-shared/src/main/resources/AppText_Es.json index d3e2e67c25..7cdcd77f9d 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/resources/AppText_Es.json +++ b/actor-sdk/sdk-core/core/core-shared/src/main/resources/AppText_Es.json @@ -100,6 +100,27 @@ "avatar_changed": "{name} modificada la foto de grupo", "avatar_removed": "{name} eliminada la foto de grupo" }, + "channels": { + "created": "Creado el canal", + "invited": "{name} invitó a {name_added}", + "joined": "{name} se unió al canal", + "kicked": "{name} expulsaste a {name_kicked}", + "left": "{name} elimino el canal", + "title_changed": { + "full": "{name} cambio el nombre del canal a \"{title}\"", + "compact": "{name} a modificado el nombre del canal" + }, + "topic_changed": { + "full": "{name} changed the canal topic to \"{topic}\"", + "compact": "{name} changed the canal topic" + }, + "about_changed": { + "full": "{name} changed the canal about to \"{about}\"", + "compact": "{name} changed the canal about" + }, + "avatar_changed": "{name} modificada la foto de canal", + "avatar_removed": "{name} eliminada la foto de canal" + }, "calls": { "missed": "Llamada perdida", "ended": "Llamada finalizada" diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/resources/AppText_Fa.json b/actor-sdk/sdk-core/core/core-shared/src/main/resources/AppText_Fa.json index 0a6b3873b2..0d663fece5 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/resources/AppText_Fa.json +++ b/actor-sdk/sdk-core/core/core-shared/src/main/resources/AppText_Fa.json @@ -94,6 +94,27 @@ "avatar_changed": "{name} تصویر گروه را عوض کرد", "avatar_removed": "{name} تصویر گروه را عوض کرد" }, + "channels": { + "created": " گروه را ایجاد کرد", + "invited": "{name} {name_added} را دعوت کرد", + "joined": "{name} به گروه پیوست", + "kicked": "{name} {name_kicked} را از گروه حذف کرد", + "left": "{name} از گروه رفت", + "title_changed": { + "full": "{name} نام گروه را با\"{title}\" جایگزین کرد", + "compact": "{name} نام گروه را جایگزین کرد" + }, + "topic_changed": { + "full": "جناب {name} موضوع گروه را با \"{topic}\" جای‌گزین کردید", + "compact": "موضوع گروه را عوض کرد {name}" + }, + "about_changed": { + "full": "جناب {name} متن درباره گروه را با \"{about}\" جای‌گزین کردید", + "compact": "متن درباره گروه را عوض کرد {name}" + }, + "avatar_changed": "{name} تصویر گروه را عوض کرد", + "avatar_removed": "{name} تصویر گروه را عوض کرد" + }, "calls": { "missed": "تماس از دست رفته", "ended": "تماس تمام شد" diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/resources/AppText_Pt.json b/actor-sdk/sdk-core/core/core-shared/src/main/resources/AppText_Pt.json index b2704c9990..f42f5e152e 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/resources/AppText_Pt.json +++ b/actor-sdk/sdk-core/core/core-shared/src/main/resources/AppText_Pt.json @@ -107,6 +107,27 @@ "avatar_changed": "{name} alterou a foto do grupo", "avatar_removed": "{name} removeu a foto do grupo" }, + "channels": { + "created": "Criou o canal", + "invited": "{name} convidado {name_added}", + "joined": "{name} entrou no canal", + "kicked": "{name} removido {name_kicked}", + "left": "{name} saiu do canal", + "title_changed": { + "full": "{name} alterou o nome do canal para \"{title}\"", + "compact": "{name} alterou o nome do canal" + }, + "topic_changed": { + "full": "{name} changed the canal topic to \"{topic}\"", + "compact": "{name} changed the canal topic" + }, + "about_changed": { + "full": "{name} changed the canal about to \"{about}\"", + "compact": "{name} changed the canal about" + }, + "avatar_changed": "{name} alterou a foto do canal", + "avatar_removed": "{name} removeu a foto do canal" + }, "calls": { "missed": "Missed call", "ended": "Call ended" diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/resources/AppText_Ru.json b/actor-sdk/sdk-core/core/core-shared/src/main/resources/AppText_Ru.json index b4793ec1b4..0f8bc7a55e 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/resources/AppText_Ru.json +++ b/actor-sdk/sdk-core/core/core-shared/src/main/resources/AppText_Ru.json @@ -160,10 +160,10 @@ "other": "{name} изменил(-а) название группы на \"{title}\"" }, "compact": { - "you": "{name} изменили тему группы", - "male": "{name} изменил тему группы", - "female": "{name} изменила тему группы", - "other": "{name} изменил(-а) тему группы" + "you": "{name} изменили название группы", + "male": "{name} изменил название группы", + "female": "{name} изменила название группы", + "other": "{name} изменил(-а) название группы" } }, "topic_changed": { @@ -207,6 +207,87 @@ "other": "{name} удалил(-а) фото группы" } }, + "channels": { + "created": "Создан канал", + "invited": { + "you": "{name} пригласили {name_added}", + "male": "{name} пригласил {name_added}", + "female": "{name} пригласила {name_added}", + "other": "{name} пригласил(-а) {name_added}" + }, + "joined": { + "you": "{name} вошли в канал", + "male": "{name} вошел в канал", + "female": "{name} вошла в канал", + "other": "{name} вошел(-шла) в канал" + }, + "kicked": { + "you": "{name} исключили {name_kicked}", + "male": "{name} исключил {name_kicked}", + "female": "{name} исключила {name_kicked}", + "other": "{name} исключил(-а) {name_kicked}" + }, + "left": { + "you": "{name} покинули канал", + "male": "{name} покинул канал", + "female": "{name} покинула канал", + "other": "{name} покинул(-а) канал" + }, + "title_changed": { + "full": { + "you": "{name} изменили название канала на \"{title}\"", + "male": "{name} изменил название канала на \"{title}\"", + "female": "{name} изменила название канала на \"{title}\"", + "other": "{name} изменил(-а) название канала на \"{title}\"" + }, + "compact": { + "you": "{name} изменили название канала", + "male": "{name} изменил название канала", + "female": "{name} изменила название канала", + "other": "{name} изменил(-а) название канала" + } + }, + "topic_changed": { + "full": { + "you": "{name} изменили тему канала на \"{title}\"", + "male": "{name} изменил тему канала на \"{title}\"", + "female": "{name} изменила тему канала на \"{title}\"", + "other": "{name} изменил(-а) тему канала на \"{title}\"" + }, + "compact": { + "you": "{name} изменили тему канала", + "male": "{name} изменил тему канала", + "female": "{name} изменила тему канала", + "other": "{name} изменил(-а) тему канала" + } + }, + "about_changed": { + "full": { + "you": "{name} изменили описание канала на \"{title}\"", + "male": "{name} изменил описание канала на \"{title}\"", + "female": "{name} изменила описание канала на \"{title}\"", + "other": "{name} изменил(-а) описание канала на \"{title}\"" + }, + "compact": { + "you": "{name} изменили канала канала", + "male": "{name} изменил описание канала", + "female": "{name} изменила описание канала", + "other": "{name} изменил(-а) описание канала" + } + }, + "avatar_changed": { + "you": "{name} изменили фото канала", + "male": "{name} изменил фото канала", + "female": "{name} изменила фото канала", + "other": "{name} изменил(-а) фото канала" + }, + "avatar_removed": { + "you": "{name} удалили фото канала", + "male": "{name} удалил фото канала", + "female": "{name} удалила фото канала", + "other": "{name} удалил(-а) фото канала" + } + }, "calls": { "missed": "Пропущен вызов", "ended": "Вызов завершен" diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/resources/AppText_Zn.json b/actor-sdk/sdk-core/core/core-shared/src/main/resources/AppText_Zn.json index 38a30bf975..f253e8b0ee 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/resources/AppText_Zn.json +++ b/actor-sdk/sdk-core/core/core-shared/src/main/resources/AppText_Zn.json @@ -101,6 +101,27 @@ "avatar_changed": "{name}修改了群组头像", "avatar_removed": "{name}删除了群组头像" }, + "channels": { + "created": "{name}创建了这个群组", + "invited": "{name}邀请{name_added}", + "joined": "{name}加入群组", + "kicked": "{name}移出{name_kicked}", + "left": "{name}退出群组", + "title_changed": { + "full": "{name}修改为名称为:\"{title}\"", + "compact": "{name}修改了群组名称" + }, + "topic_changed": { + "full": "{name} changed the group topic to \"{topic}\"", + "compact": "{name} changed the group topic" + }, + "about_changed": { + "full": "{name} changed the group about to \"{about}\"", + "compact": "{name} changed the group about" + }, + "avatar_changed": "{name}修改了群组头像", + "avatar_removed": "{name}删除了群组头像" + }, "calls": { "missed": "未接电话", "ended": "拨号结束" From 00d8b4e9a07379caa29531383aea7a1d13eef4e4 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Fri, 15 Jul 2016 16:21:37 +0300 Subject: [PATCH 038/414] feat(core): Handling new updates for group api v2 --- .../im/actor/core/api/ApiAdminSettings.java | 88 ++++++ .../java/im/actor/core/api/ApiGroupFull.java | 97 ++++++- .../im/actor/core/api/parser/RpcParser.java | 5 + .../actor/core/api/parser/UpdatesParser.java | 6 + .../core/api/rpc/RequestDismissUserAdmin.java | 78 ++++++ .../api/rpc/RequestEditGroupShortName.java | 77 ++++++ .../api/rpc/RequestLoadAdminSettings.java | 65 +++++ .../api/rpc/RequestSaveAdminSettings.java | 78 ++++++ .../api/rpc/RequestTransferOwnership.java | 14 +- .../api/rpc/ResponseLoadAdminSettings.java | 64 +++++ ...pdateGroupCanEditAdminSettingsChanged.java | 70 +++++ .../UpdateGroupCanEditAdminsChanged.java | 69 +++++ .../UpdateGroupCanEditInfoChanged.java | 70 +++++ .../UpdateGroupCanEditUsernameChanged.java | 70 +++++ .../UpdateGroupCanViewAdminsChanged.java | 69 +++++ .../updates/UpdateGroupShortNameChanged.java | 73 +++++ .../main/java/im/actor/core/entity/Group.java | 261 +++++++++++++++++- .../core/modules/groups/GroupsModule.java | 2 +- .../core/modules/groups/GroupsProcessor.java | 13 +- .../modules/groups/router/GroupRouter.java | 72 ++++- 20 files changed, 1308 insertions(+), 33 deletions(-) create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiAdminSettings.java create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/RequestDismissUserAdmin.java create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/RequestEditGroupShortName.java create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/RequestLoadAdminSettings.java create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/RequestSaveAdminSettings.java create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/ResponseLoadAdminSettings.java create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanEditAdminSettingsChanged.java create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanEditAdminsChanged.java create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanEditInfoChanged.java create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanEditUsernameChanged.java create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanViewAdminsChanged.java create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupShortNameChanged.java diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiAdminSettings.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiAdminSettings.java new file mode 100644 index 0000000000..00aced48a1 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiAdminSettings.java @@ -0,0 +1,88 @@ +package im.actor.core.api; +/* + * Generated by the Actor API Scheme generator. DO NOT EDIT! + */ + +import im.actor.runtime.bser.*; +import im.actor.runtime.collections.*; +import static im.actor.runtime.bser.Utils.*; +import im.actor.core.network.parser.*; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.NotNull; +import com.google.j2objc.annotations.ObjectiveCName; +import java.io.IOException; +import java.util.List; +import java.util.ArrayList; + +public class ApiAdminSettings extends BserObject { + + private boolean showAdminsToMembers; + private boolean canMembersInvite; + private boolean canMembersEditGroupInfo; + private boolean canAdminsEditGroupInfo; + + public ApiAdminSettings(boolean showAdminsToMembers, boolean canMembersInvite, boolean canMembersEditGroupInfo, boolean canAdminsEditGroupInfo) { + this.showAdminsToMembers = showAdminsToMembers; + this.canMembersInvite = canMembersInvite; + this.canMembersEditGroupInfo = canMembersEditGroupInfo; + this.canAdminsEditGroupInfo = canAdminsEditGroupInfo; + } + + public ApiAdminSettings() { + + } + + public boolean showAdminsToMembers() { + return this.showAdminsToMembers; + } + + public boolean canMembersInvite() { + return this.canMembersInvite; + } + + public boolean canMembersEditGroupInfo() { + return this.canMembersEditGroupInfo; + } + + public boolean canAdminsEditGroupInfo() { + return this.canAdminsEditGroupInfo; + } + + @Override + public void parse(BserValues values) throws IOException { + this.showAdminsToMembers = values.getBool(1); + this.canMembersInvite = values.getBool(2); + this.canMembersEditGroupInfo = values.getBool(3); + this.canAdminsEditGroupInfo = values.getBool(4); + if (values.hasRemaining()) { + setUnmappedObjects(values.buildRemaining()); + } + } + + @Override + public void serialize(BserWriter writer) throws IOException { + writer.writeBool(1, this.showAdminsToMembers); + writer.writeBool(2, this.canMembersInvite); + writer.writeBool(3, this.canMembersEditGroupInfo); + writer.writeBool(4, this.canAdminsEditGroupInfo); + if (this.getUnmappedObjects() != null) { + SparseArray unmapped = this.getUnmappedObjects(); + for (int i = 0; i < unmapped.size(); i++) { + int key = unmapped.keyAt(i); + writer.writeUnmapped(key, unmapped.get(key)); + } + } + } + + @Override + public String toString() { + String res = "struct AdminSettings{"; + res += "showAdminsToMembers=" + this.showAdminsToMembers; + res += ", canMembersInvite=" + this.canMembersInvite; + res += ", canMembersEditGroupInfo=" + this.canMembersEditGroupInfo; + res += ", canAdminsEditGroupInfo=" + this.canAdminsEditGroupInfo; + res += "}"; + return res; + } + +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiGroupFull.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiGroupFull.java index 843e07075d..72ac66feb2 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiGroupFull.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiGroupFull.java @@ -18,7 +18,7 @@ public class ApiGroupFull extends BserObject { private int id; private long createDate; - private int ownerUid; + private Integer ownerUid; private List members; private String theme; private String about; @@ -27,8 +27,14 @@ public class ApiGroupFull extends BserObject { private Boolean canViewMembers; private Boolean canInvitePeople; private Boolean isSharedHistory; - - public ApiGroupFull(int id, long createDate, int ownerUid, @NotNull List members, @Nullable String theme, @Nullable String about, @Nullable ApiMapValue ext, @Nullable Boolean isAsyncMembers, @Nullable Boolean canViewMembers, @Nullable Boolean canInvitePeople, @Nullable Boolean isSharedHistory) { + private Boolean canEditGroupInfo; + private String shortName; + private Boolean canEditShortName; + private Boolean canEditAdminList; + private Boolean canViewAdminList; + private Boolean canEditAdminSettings; + + public ApiGroupFull(int id, long createDate, @Nullable Integer ownerUid, @NotNull List members, @Nullable String theme, @Nullable String about, @Nullable ApiMapValue ext, @Nullable Boolean isAsyncMembers, @Nullable Boolean canViewMembers, @Nullable Boolean canInvitePeople, @Nullable Boolean isSharedHistory, @Nullable Boolean canEditGroupInfo, @Nullable String shortName, @Nullable Boolean canEditShortName, @Nullable Boolean canEditAdminList, @Nullable Boolean canViewAdminList, @Nullable Boolean canEditAdminSettings) { this.id = id; this.createDate = createDate; this.ownerUid = ownerUid; @@ -40,6 +46,12 @@ public ApiGroupFull(int id, long createDate, int ownerUid, @NotNull List _members = new ArrayList(); for (int i = 0; i < values.getRepeatedCount(12); i ++) { _members.add(new ApiMember()); @@ -115,13 +158,24 @@ public void parse(BserValues values) throws IOException { this.canViewMembers = values.optBool(8); this.canInvitePeople = values.optBool(9); this.isSharedHistory = values.optBool(10); + this.canEditGroupInfo = values.optBool(13); + this.shortName = values.optString(14); + this.canEditShortName = values.optBool(15); + this.canEditAdminList = values.optBool(16); + this.canViewAdminList = values.optBool(17); + this.canEditAdminSettings = values.optBool(18); + if (values.hasRemaining()) { + setUnmappedObjects(values.buildRemaining()); + } } @Override public void serialize(BserWriter writer) throws IOException { writer.writeInt(1, this.id); writer.writeLong(6, this.createDate); - writer.writeInt(5, this.ownerUid); + if (this.ownerUid != null) { + writer.writeInt(5, this.ownerUid); + } writer.writeRepeatedObj(12, this.members); if (this.theme != null) { writer.writeString(2, this.theme); @@ -144,6 +198,31 @@ public void serialize(BserWriter writer) throws IOException { if (this.isSharedHistory != null) { writer.writeBool(10, this.isSharedHistory); } + if (this.canEditGroupInfo != null) { + writer.writeBool(13, this.canEditGroupInfo); + } + if (this.shortName != null) { + writer.writeString(14, this.shortName); + } + if (this.canEditShortName != null) { + writer.writeBool(15, this.canEditShortName); + } + if (this.canEditAdminList != null) { + writer.writeBool(16, this.canEditAdminList); + } + if (this.canViewAdminList != null) { + writer.writeBool(17, this.canViewAdminList); + } + if (this.canEditAdminSettings != null) { + writer.writeBool(18, this.canEditAdminSettings); + } + if (this.getUnmappedObjects() != null) { + SparseArray unmapped = this.getUnmappedObjects(); + for (int i = 0; i < unmapped.size(); i++) { + int key = unmapped.keyAt(i); + writer.writeUnmapped(key, unmapped.get(key)); + } + } } @Override @@ -159,6 +238,12 @@ public String toString() { res += ", canViewMembers=" + this.canViewMembers; res += ", canInvitePeople=" + this.canInvitePeople; res += ", isSharedHistory=" + this.isSharedHistory; + res += ", canEditGroupInfo=" + this.canEditGroupInfo; + res += ", shortName=" + this.shortName; + res += ", canEditShortName=" + this.canEditShortName; + res += ", canEditAdminList=" + this.canEditAdminList; + res += ", canViewAdminList=" + this.canViewAdminList; + res += ", canEditAdminSettings=" + this.canEditAdminSettings; res += "}"; return res; } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/parser/RpcParser.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/parser/RpcParser.java index ce1b5d0096..dce6cf7ff6 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/parser/RpcParser.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/parser/RpcParser.java @@ -82,13 +82,17 @@ public RpcScope read(int type, byte[] payload) throws IOException { case 85: return RequestEditGroupTitle.fromBytes(payload); case 86: return RequestEditGroupAvatar.fromBytes(payload); case 101: return RequestRemoveGroupAvatar.fromBytes(payload); + case 2793: return RequestEditGroupShortName.fromBytes(payload); case 211: return RequestEditGroupTopic.fromBytes(payload); case 213: return RequestEditGroupAbout.fromBytes(payload); case 69: return RequestInviteUser.fromBytes(payload); case 70: return RequestLeaveGroup.fromBytes(payload); case 71: return RequestKickUser.fromBytes(payload); case 2784: return RequestMakeUserAdmin.fromBytes(payload); + case 2791: return RequestDismissUserAdmin.fromBytes(payload); case 2789: return RequestTransferOwnership.fromBytes(payload); + case 2790: return RequestLoadAdminSettings.fromBytes(payload); + case 2792: return RequestSaveAdminSettings.fromBytes(payload); case 177: return RequestGetGroupInviteUrl.fromBytes(payload); case 179: return RequestRevokeInviteUrl.fromBytes(payload); case 180: return RequestJoinGroup.fromBytes(payload); @@ -197,6 +201,7 @@ public RpcScope read(int type, byte[] payload) throws IOException { case 2787: return ResponseLoadMembers.fromBytes(payload); case 216: return ResponseCreateGroup.fromBytes(payload); case 115: return ResponseEditGroupAvatar.fromBytes(payload); + case 2794: return ResponseLoadAdminSettings.fromBytes(payload); case 178: return ResponseInviteUrl.fromBytes(payload); case 181: return ResponseJoinGroup.fromBytes(payload); case 66: return ResponseCreateGroupObsolete.fromBytes(payload); diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/parser/UpdatesParser.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/parser/UpdatesParser.java index 4a3ce5315c..600d42a4b5 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/parser/UpdatesParser.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/parser/UpdatesParser.java @@ -55,11 +55,17 @@ public Update read(int type, byte[] payload) throws IOException { case 2617: return UpdateGroupAboutChanged.fromBytes(payload); case 2613: return UpdateGroupExtChanged.fromBytes(payload); case 2618: return UpdateGroupFullExtChanged.fromBytes(payload); + case 2628: return UpdateGroupShortNameChanged.fromBytes(payload); case 2619: return UpdateGroupOwnerChanged.fromBytes(payload); case 2620: return UpdateGroupHistoryShared.fromBytes(payload); case 2624: return UpdateGroupCanSendMessagesChanged.fromBytes(payload); case 2625: return UpdateGroupCanViewMembersChanged.fromBytes(payload); case 2626: return UpdateGroupCanInviteMembersChanged.fromBytes(payload); + case 2631: return UpdateGroupCanEditInfoChanged.fromBytes(payload); + case 2632: return UpdateGroupCanEditUsernameChanged.fromBytes(payload); + case 2633: return UpdateGroupCanEditAdminsChanged.fromBytes(payload); + case 2640: return UpdateGroupCanViewAdminsChanged.fromBytes(payload); + case 2641: return UpdateGroupCanEditAdminSettingsChanged.fromBytes(payload); case 2612: return UpdateGroupMemberChanged.fromBytes(payload); case 2615: return UpdateGroupMembersBecameAsync.fromBytes(payload); case 2614: return UpdateGroupMembersUpdated.fromBytes(payload); diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/RequestDismissUserAdmin.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/RequestDismissUserAdmin.java new file mode 100644 index 0000000000..30833991c6 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/RequestDismissUserAdmin.java @@ -0,0 +1,78 @@ +package im.actor.core.api.rpc; +/* + * Generated by the Actor API Scheme generator. DO NOT EDIT! + */ + +import im.actor.runtime.bser.*; +import im.actor.runtime.collections.*; +import static im.actor.runtime.bser.Utils.*; +import im.actor.core.network.parser.*; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.NotNull; +import com.google.j2objc.annotations.ObjectiveCName; +import java.io.IOException; +import java.util.List; +import java.util.ArrayList; +import im.actor.core.api.*; + +public class RequestDismissUserAdmin extends Request { + + public static final int HEADER = 0xae7; + public static RequestDismissUserAdmin fromBytes(byte[] data) throws IOException { + return Bser.parse(new RequestDismissUserAdmin(), data); + } + + private ApiGroupOutPeer groupPeer; + private ApiUserOutPeer userPeer; + + public RequestDismissUserAdmin(@NotNull ApiGroupOutPeer groupPeer, @NotNull ApiUserOutPeer userPeer) { + this.groupPeer = groupPeer; + this.userPeer = userPeer; + } + + public RequestDismissUserAdmin() { + + } + + @NotNull + public ApiGroupOutPeer getGroupPeer() { + return this.groupPeer; + } + + @NotNull + public ApiUserOutPeer getUserPeer() { + return this.userPeer; + } + + @Override + public void parse(BserValues values) throws IOException { + this.groupPeer = values.getObj(1, new ApiGroupOutPeer()); + this.userPeer = values.getObj(2, new ApiUserOutPeer()); + } + + @Override + public void serialize(BserWriter writer) throws IOException { + if (this.groupPeer == null) { + throw new IOException(); + } + writer.writeObject(1, this.groupPeer); + if (this.userPeer == null) { + throw new IOException(); + } + writer.writeObject(2, this.userPeer); + } + + @Override + public String toString() { + String res = "rpc DismissUserAdmin{"; + res += "groupPeer=" + this.groupPeer; + res += ", userPeer=" + this.userPeer; + res += "}"; + return res; + } + + @Override + public int getHeaderKey() { + return HEADER; + } +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/RequestEditGroupShortName.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/RequestEditGroupShortName.java new file mode 100644 index 0000000000..fc5d139256 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/RequestEditGroupShortName.java @@ -0,0 +1,77 @@ +package im.actor.core.api.rpc; +/* + * Generated by the Actor API Scheme generator. DO NOT EDIT! + */ + +import im.actor.runtime.bser.*; +import im.actor.runtime.collections.*; +import static im.actor.runtime.bser.Utils.*; +import im.actor.core.network.parser.*; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.NotNull; +import com.google.j2objc.annotations.ObjectiveCName; +import java.io.IOException; +import java.util.List; +import java.util.ArrayList; +import im.actor.core.api.*; + +public class RequestEditGroupShortName extends Request { + + public static final int HEADER = 0xae9; + public static RequestEditGroupShortName fromBytes(byte[] data) throws IOException { + return Bser.parse(new RequestEditGroupShortName(), data); + } + + private ApiGroupOutPeer groupPeer; + private String shortName; + + public RequestEditGroupShortName(@NotNull ApiGroupOutPeer groupPeer, @Nullable String shortName) { + this.groupPeer = groupPeer; + this.shortName = shortName; + } + + public RequestEditGroupShortName() { + + } + + @NotNull + public ApiGroupOutPeer getGroupPeer() { + return this.groupPeer; + } + + @Nullable + public String getShortName() { + return this.shortName; + } + + @Override + public void parse(BserValues values) throws IOException { + this.groupPeer = values.getObj(1, new ApiGroupOutPeer()); + this.shortName = values.optString(2); + } + + @Override + public void serialize(BserWriter writer) throws IOException { + if (this.groupPeer == null) { + throw new IOException(); + } + writer.writeObject(1, this.groupPeer); + if (this.shortName != null) { + writer.writeString(2, this.shortName); + } + } + + @Override + public String toString() { + String res = "rpc EditGroupShortName{"; + res += "groupPeer=" + this.groupPeer; + res += ", shortName=" + this.shortName; + res += "}"; + return res; + } + + @Override + public int getHeaderKey() { + return HEADER; + } +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/RequestLoadAdminSettings.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/RequestLoadAdminSettings.java new file mode 100644 index 0000000000..3cf4818a73 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/RequestLoadAdminSettings.java @@ -0,0 +1,65 @@ +package im.actor.core.api.rpc; +/* + * Generated by the Actor API Scheme generator. DO NOT EDIT! + */ + +import im.actor.runtime.bser.*; +import im.actor.runtime.collections.*; +import static im.actor.runtime.bser.Utils.*; +import im.actor.core.network.parser.*; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.NotNull; +import com.google.j2objc.annotations.ObjectiveCName; +import java.io.IOException; +import java.util.List; +import java.util.ArrayList; +import im.actor.core.api.*; + +public class RequestLoadAdminSettings extends Request { + + public static final int HEADER = 0xae6; + public static RequestLoadAdminSettings fromBytes(byte[] data) throws IOException { + return Bser.parse(new RequestLoadAdminSettings(), data); + } + + private ApiGroupOutPeer groupPeer; + + public RequestLoadAdminSettings(@NotNull ApiGroupOutPeer groupPeer) { + this.groupPeer = groupPeer; + } + + public RequestLoadAdminSettings() { + + } + + @NotNull + public ApiGroupOutPeer getGroupPeer() { + return this.groupPeer; + } + + @Override + public void parse(BserValues values) throws IOException { + this.groupPeer = values.getObj(1, new ApiGroupOutPeer()); + } + + @Override + public void serialize(BserWriter writer) throws IOException { + if (this.groupPeer == null) { + throw new IOException(); + } + writer.writeObject(1, this.groupPeer); + } + + @Override + public String toString() { + String res = "rpc LoadAdminSettings{"; + res += "groupPeer=" + this.groupPeer; + res += "}"; + return res; + } + + @Override + public int getHeaderKey() { + return HEADER; + } +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/RequestSaveAdminSettings.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/RequestSaveAdminSettings.java new file mode 100644 index 0000000000..0639937ed5 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/RequestSaveAdminSettings.java @@ -0,0 +1,78 @@ +package im.actor.core.api.rpc; +/* + * Generated by the Actor API Scheme generator. DO NOT EDIT! + */ + +import im.actor.runtime.bser.*; +import im.actor.runtime.collections.*; +import static im.actor.runtime.bser.Utils.*; +import im.actor.core.network.parser.*; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.NotNull; +import com.google.j2objc.annotations.ObjectiveCName; +import java.io.IOException; +import java.util.List; +import java.util.ArrayList; +import im.actor.core.api.*; + +public class RequestSaveAdminSettings extends Request { + + public static final int HEADER = 0xae8; + public static RequestSaveAdminSettings fromBytes(byte[] data) throws IOException { + return Bser.parse(new RequestSaveAdminSettings(), data); + } + + private ApiGroupOutPeer groupPeer; + private ApiAdminSettings settings; + + public RequestSaveAdminSettings(@NotNull ApiGroupOutPeer groupPeer, @NotNull ApiAdminSettings settings) { + this.groupPeer = groupPeer; + this.settings = settings; + } + + public RequestSaveAdminSettings() { + + } + + @NotNull + public ApiGroupOutPeer getGroupPeer() { + return this.groupPeer; + } + + @NotNull + public ApiAdminSettings getSettings() { + return this.settings; + } + + @Override + public void parse(BserValues values) throws IOException { + this.groupPeer = values.getObj(1, new ApiGroupOutPeer()); + this.settings = values.getObj(2, new ApiAdminSettings()); + } + + @Override + public void serialize(BserWriter writer) throws IOException { + if (this.groupPeer == null) { + throw new IOException(); + } + writer.writeObject(1, this.groupPeer); + if (this.settings == null) { + throw new IOException(); + } + writer.writeObject(2, this.settings); + } + + @Override + public String toString() { + String res = "rpc SaveAdminSettings{"; + res += "groupPeer=" + this.groupPeer; + res += ", settings=" + this.settings; + res += "}"; + return res; + } + + @Override + public int getHeaderKey() { + return HEADER; + } +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/RequestTransferOwnership.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/RequestTransferOwnership.java index 5583c9b4fe..7b0ab441bb 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/RequestTransferOwnership.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/RequestTransferOwnership.java @@ -23,9 +23,9 @@ public static RequestTransferOwnership fromBytes(byte[] data) throws IOException } private ApiGroupOutPeer groupPeer; - private int newOwner; + private ApiUserOutPeer newOwner; - public RequestTransferOwnership(@NotNull ApiGroupOutPeer groupPeer, int newOwner) { + public RequestTransferOwnership(@NotNull ApiGroupOutPeer groupPeer, @NotNull ApiUserOutPeer newOwner) { this.groupPeer = groupPeer; this.newOwner = newOwner; } @@ -39,14 +39,15 @@ public ApiGroupOutPeer getGroupPeer() { return this.groupPeer; } - public int getNewOwner() { + @NotNull + public ApiUserOutPeer getNewOwner() { return this.newOwner; } @Override public void parse(BserValues values) throws IOException { this.groupPeer = values.getObj(1, new ApiGroupOutPeer()); - this.newOwner = values.getInt(2); + this.newOwner = values.getObj(2, new ApiUserOutPeer()); } @Override @@ -55,7 +56,10 @@ public void serialize(BserWriter writer) throws IOException { throw new IOException(); } writer.writeObject(1, this.groupPeer); - writer.writeInt(2, this.newOwner); + if (this.newOwner == null) { + throw new IOException(); + } + writer.writeObject(2, this.newOwner); } @Override diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/ResponseLoadAdminSettings.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/ResponseLoadAdminSettings.java new file mode 100644 index 0000000000..3bb736dc6c --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/ResponseLoadAdminSettings.java @@ -0,0 +1,64 @@ +package im.actor.core.api.rpc; +/* + * Generated by the Actor API Scheme generator. DO NOT EDIT! + */ + +import im.actor.runtime.bser.*; +import im.actor.runtime.collections.*; +import static im.actor.runtime.bser.Utils.*; +import im.actor.core.network.parser.*; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.NotNull; +import com.google.j2objc.annotations.ObjectiveCName; +import java.io.IOException; +import java.util.List; +import java.util.ArrayList; +import im.actor.core.api.*; + +public class ResponseLoadAdminSettings extends Response { + + public static final int HEADER = 0xaea; + public static ResponseLoadAdminSettings fromBytes(byte[] data) throws IOException { + return Bser.parse(new ResponseLoadAdminSettings(), data); + } + + private ApiAdminSettings settings; + + public ResponseLoadAdminSettings(@NotNull ApiAdminSettings settings) { + this.settings = settings; + } + + public ResponseLoadAdminSettings() { + + } + + @NotNull + public ApiAdminSettings getSettings() { + return this.settings; + } + + @Override + public void parse(BserValues values) throws IOException { + this.settings = values.getObj(1, new ApiAdminSettings()); + } + + @Override + public void serialize(BserWriter writer) throws IOException { + if (this.settings == null) { + throw new IOException(); + } + writer.writeObject(1, this.settings); + } + + @Override + public String toString() { + String res = "tuple LoadAdminSettings{"; + res += "}"; + return res; + } + + @Override + public int getHeaderKey() { + return HEADER; + } +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanEditAdminSettingsChanged.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanEditAdminSettingsChanged.java new file mode 100644 index 0000000000..364b5f462a --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanEditAdminSettingsChanged.java @@ -0,0 +1,70 @@ +package im.actor.core.api.updates; +/* + * Generated by the Actor API Scheme generator. DO NOT EDIT! + */ + +import im.actor.runtime.bser.*; +import im.actor.runtime.collections.*; +import static im.actor.runtime.bser.Utils.*; +import im.actor.core.network.parser.*; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.NotNull; +import com.google.j2objc.annotations.ObjectiveCName; +import java.io.IOException; +import java.util.List; +import java.util.ArrayList; +import im.actor.core.api.*; + +public class UpdateGroupCanEditAdminSettingsChanged extends Update { + + public static final int HEADER = 0xa51; + public static UpdateGroupCanEditAdminSettingsChanged fromBytes(byte[] data) throws IOException { + return Bser.parse(new UpdateGroupCanEditAdminSettingsChanged(), data); + } + + private int groupId; + private boolean canEditAdminSettings; + + public UpdateGroupCanEditAdminSettingsChanged(int groupId, boolean canEditAdminSettings) { + this.groupId = groupId; + this.canEditAdminSettings = canEditAdminSettings; + } + + public UpdateGroupCanEditAdminSettingsChanged() { + + } + + public int getGroupId() { + return this.groupId; + } + + public boolean canEditAdminSettings() { + return this.canEditAdminSettings; + } + + @Override + public void parse(BserValues values) throws IOException { + this.groupId = values.getInt(1); + this.canEditAdminSettings = values.getBool(2); + } + + @Override + public void serialize(BserWriter writer) throws IOException { + writer.writeInt(1, this.groupId); + writer.writeBool(2, this.canEditAdminSettings); + } + + @Override + public String toString() { + String res = "update GroupCanEditAdminSettingsChanged{"; + res += "groupId=" + this.groupId; + res += ", canEditAdminSettings=" + this.canEditAdminSettings; + res += "}"; + return res; + } + + @Override + public int getHeaderKey() { + return HEADER; + } +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanEditAdminsChanged.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanEditAdminsChanged.java new file mode 100644 index 0000000000..a243f19194 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanEditAdminsChanged.java @@ -0,0 +1,69 @@ +package im.actor.core.api.updates; +/* + * Generated by the Actor API Scheme generator. DO NOT EDIT! + */ + +import im.actor.runtime.bser.*; +import im.actor.runtime.collections.*; +import static im.actor.runtime.bser.Utils.*; +import im.actor.core.network.parser.*; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.NotNull; +import com.google.j2objc.annotations.ObjectiveCName; +import java.io.IOException; +import java.util.List; +import java.util.ArrayList; +import im.actor.core.api.*; + +public class UpdateGroupCanEditAdminsChanged extends Update { + + public static final int HEADER = 0xa49; + public static UpdateGroupCanEditAdminsChanged fromBytes(byte[] data) throws IOException { + return Bser.parse(new UpdateGroupCanEditAdminsChanged(), data); + } + + private int groupId; + private boolean canAssignAdmins; + + public UpdateGroupCanEditAdminsChanged(int groupId, boolean canAssignAdmins) { + this.groupId = groupId; + this.canAssignAdmins = canAssignAdmins; + } + + public UpdateGroupCanEditAdminsChanged() { + + } + + public int getGroupId() { + return this.groupId; + } + + public boolean canAssignAdmins() { + return this.canAssignAdmins; + } + + @Override + public void parse(BserValues values) throws IOException { + this.groupId = values.getInt(1); + this.canAssignAdmins = values.getBool(2); + } + + @Override + public void serialize(BserWriter writer) throws IOException { + writer.writeInt(1, this.groupId); + writer.writeBool(2, this.canAssignAdmins); + } + + @Override + public String toString() { + String res = "update GroupCanEditAdminsChanged{"; + res += "groupId=" + this.groupId; + res += "}"; + return res; + } + + @Override + public int getHeaderKey() { + return HEADER; + } +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanEditInfoChanged.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanEditInfoChanged.java new file mode 100644 index 0000000000..66ed344c64 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanEditInfoChanged.java @@ -0,0 +1,70 @@ +package im.actor.core.api.updates; +/* + * Generated by the Actor API Scheme generator. DO NOT EDIT! + */ + +import im.actor.runtime.bser.*; +import im.actor.runtime.collections.*; +import static im.actor.runtime.bser.Utils.*; +import im.actor.core.network.parser.*; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.NotNull; +import com.google.j2objc.annotations.ObjectiveCName; +import java.io.IOException; +import java.util.List; +import java.util.ArrayList; +import im.actor.core.api.*; + +public class UpdateGroupCanEditInfoChanged extends Update { + + public static final int HEADER = 0xa47; + public static UpdateGroupCanEditInfoChanged fromBytes(byte[] data) throws IOException { + return Bser.parse(new UpdateGroupCanEditInfoChanged(), data); + } + + private int groupId; + private boolean canEditGroup; + + public UpdateGroupCanEditInfoChanged(int groupId, boolean canEditGroup) { + this.groupId = groupId; + this.canEditGroup = canEditGroup; + } + + public UpdateGroupCanEditInfoChanged() { + + } + + public int getGroupId() { + return this.groupId; + } + + public boolean canEditGroup() { + return this.canEditGroup; + } + + @Override + public void parse(BserValues values) throws IOException { + this.groupId = values.getInt(1); + this.canEditGroup = values.getBool(2); + } + + @Override + public void serialize(BserWriter writer) throws IOException { + writer.writeInt(1, this.groupId); + writer.writeBool(2, this.canEditGroup); + } + + @Override + public String toString() { + String res = "update GroupCanEditInfoChanged{"; + res += "groupId=" + this.groupId; + res += ", canEditGroup=" + this.canEditGroup; + res += "}"; + return res; + } + + @Override + public int getHeaderKey() { + return HEADER; + } +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanEditUsernameChanged.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanEditUsernameChanged.java new file mode 100644 index 0000000000..03097d4618 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanEditUsernameChanged.java @@ -0,0 +1,70 @@ +package im.actor.core.api.updates; +/* + * Generated by the Actor API Scheme generator. DO NOT EDIT! + */ + +import im.actor.runtime.bser.*; +import im.actor.runtime.collections.*; +import static im.actor.runtime.bser.Utils.*; +import im.actor.core.network.parser.*; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.NotNull; +import com.google.j2objc.annotations.ObjectiveCName; +import java.io.IOException; +import java.util.List; +import java.util.ArrayList; +import im.actor.core.api.*; + +public class UpdateGroupCanEditUsernameChanged extends Update { + + public static final int HEADER = 0xa48; + public static UpdateGroupCanEditUsernameChanged fromBytes(byte[] data) throws IOException { + return Bser.parse(new UpdateGroupCanEditUsernameChanged(), data); + } + + private int groupId; + private boolean canEditUsername; + + public UpdateGroupCanEditUsernameChanged(int groupId, boolean canEditUsername) { + this.groupId = groupId; + this.canEditUsername = canEditUsername; + } + + public UpdateGroupCanEditUsernameChanged() { + + } + + public int getGroupId() { + return this.groupId; + } + + public boolean canEditUsername() { + return this.canEditUsername; + } + + @Override + public void parse(BserValues values) throws IOException { + this.groupId = values.getInt(1); + this.canEditUsername = values.getBool(2); + } + + @Override + public void serialize(BserWriter writer) throws IOException { + writer.writeInt(1, this.groupId); + writer.writeBool(2, this.canEditUsername); + } + + @Override + public String toString() { + String res = "update GroupCanEditUsernameChanged{"; + res += "groupId=" + this.groupId; + res += ", canEditUsername=" + this.canEditUsername; + res += "}"; + return res; + } + + @Override + public int getHeaderKey() { + return HEADER; + } +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanViewAdminsChanged.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanViewAdminsChanged.java new file mode 100644 index 0000000000..4d6806d951 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanViewAdminsChanged.java @@ -0,0 +1,69 @@ +package im.actor.core.api.updates; +/* + * Generated by the Actor API Scheme generator. DO NOT EDIT! + */ + +import im.actor.runtime.bser.*; +import im.actor.runtime.collections.*; +import static im.actor.runtime.bser.Utils.*; +import im.actor.core.network.parser.*; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.NotNull; +import com.google.j2objc.annotations.ObjectiveCName; +import java.io.IOException; +import java.util.List; +import java.util.ArrayList; +import im.actor.core.api.*; + +public class UpdateGroupCanViewAdminsChanged extends Update { + + public static final int HEADER = 0xa50; + public static UpdateGroupCanViewAdminsChanged fromBytes(byte[] data) throws IOException { + return Bser.parse(new UpdateGroupCanViewAdminsChanged(), data); + } + + private int groupId; + private boolean canViewAdmins; + + public UpdateGroupCanViewAdminsChanged(int groupId, boolean canViewAdmins) { + this.groupId = groupId; + this.canViewAdmins = canViewAdmins; + } + + public UpdateGroupCanViewAdminsChanged() { + + } + + public int getGroupId() { + return this.groupId; + } + + public boolean canViewAdmins() { + return this.canViewAdmins; + } + + @Override + public void parse(BserValues values) throws IOException { + this.groupId = values.getInt(1); + this.canViewAdmins = values.getBool(2); + } + + @Override + public void serialize(BserWriter writer) throws IOException { + writer.writeInt(1, this.groupId); + writer.writeBool(2, this.canViewAdmins); + } + + @Override + public String toString() { + String res = "update GroupCanViewAdminsChanged{"; + res += "groupId=" + this.groupId; + res += "}"; + return res; + } + + @Override + public int getHeaderKey() { + return HEADER; + } +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupShortNameChanged.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupShortNameChanged.java new file mode 100644 index 0000000000..f5bcbedbf3 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupShortNameChanged.java @@ -0,0 +1,73 @@ +package im.actor.core.api.updates; +/* + * Generated by the Actor API Scheme generator. DO NOT EDIT! + */ + +import im.actor.runtime.bser.*; +import im.actor.runtime.collections.*; +import static im.actor.runtime.bser.Utils.*; +import im.actor.core.network.parser.*; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.NotNull; +import com.google.j2objc.annotations.ObjectiveCName; +import java.io.IOException; +import java.util.List; +import java.util.ArrayList; +import im.actor.core.api.*; + +public class UpdateGroupShortNameChanged extends Update { + + public static final int HEADER = 0xa44; + public static UpdateGroupShortNameChanged fromBytes(byte[] data) throws IOException { + return Bser.parse(new UpdateGroupShortNameChanged(), data); + } + + private int groupId; + private String shortName; + + public UpdateGroupShortNameChanged(int groupId, @Nullable String shortName) { + this.groupId = groupId; + this.shortName = shortName; + } + + public UpdateGroupShortNameChanged() { + + } + + public int getGroupId() { + return this.groupId; + } + + @Nullable + public String getShortName() { + return this.shortName; + } + + @Override + public void parse(BserValues values) throws IOException { + this.groupId = values.getInt(1); + this.shortName = values.optString(2); + } + + @Override + public void serialize(BserWriter writer) throws IOException { + writer.writeInt(1, this.groupId); + if (this.shortName != null) { + writer.writeString(2, this.shortName); + } + } + + @Override + public String toString() { + String res = "update GroupShortNameChanged{"; + res += "groupId=" + this.groupId; + res += ", shortName=" + this.shortName; + res += "}"; + return res; + } + + @Override + public int getHeaderKey() { + return HEADER; + } +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Group.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Group.java index 0811cd297e..13d836feff 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Group.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Group.java @@ -63,7 +63,7 @@ public class Group extends WrapperExtEntity implements K // @Property("readonly, nonatomic") - private int ownerId; + private Integer ownerId; @Nullable @Property("readonly, nonatomic") private String topic; @@ -82,6 +82,8 @@ public class Group extends WrapperExtEntity implements K private boolean isCanViewMembers; @Property("readonly, nonatomic") private boolean isSharedHistory; + @Property("readonly, nonatomic") + private boolean isCanEditInfo; @Property("readonly, nonatomic") private boolean haveExtension; @@ -164,7 +166,8 @@ public String getAbout() { return about; } - public int getOwnerId() { + @Nullable + public Integer getOwnerId() { return ownerId; } @@ -299,7 +302,13 @@ public Group editMembers(List members) { e.isAsyncMembers(), e.canViewMembers(), e.canInvitePeople(), - e.isSharedHistory()); + e.isSharedHistory(), + e.canEditGroupInfo(), + e.getShortName(), + e.canEditShortName(), + e.canEditAdminList(), + e.canViewAdminList(), + e.canEditAdminSettings()); fullExt.setUnmappedObjects(e.getUnmappedObjects()); } @@ -357,7 +366,13 @@ public Group editMembers(List added, List removed, int count e.isAsyncMembers(), e.canViewMembers(), e.canInvitePeople(), - e.isSharedHistory()); + e.isSharedHistory(), + e.canEditGroupInfo(), + e.getShortName(), + e.canEditShortName(), + e.canEditAdminList(), + e.canViewAdminList(), + e.canEditAdminSettings()); fullExt.setUnmappedObjects(e.getUnmappedObjects()); } @@ -407,7 +422,13 @@ public Group editMembersBecameAsync() { true, e.canViewMembers(), e.canInvitePeople(), - e.isSharedHistory()); + e.isSharedHistory(), + e.canEditGroupInfo(), + e.getShortName(), + e.canEditShortName(), + e.canEditAdminList(), + e.canViewAdminList(), + e.canEditAdminSettings()); fullExt.setUnmappedObjects(e.getUnmappedObjects()); return new Group(getWrapped(), fullExt); @@ -441,7 +462,13 @@ public Group editMemberChangedAdmin(int uid, Boolean isAdmin) { e.isAsyncMembers(), e.canViewMembers(), e.canInvitePeople(), - e.isSharedHistory()); + e.isSharedHistory(), + e.canEditGroupInfo(), + e.getShortName(), + e.canEditShortName(), + e.canEditAdminList(), + e.canViewAdminList(), + e.canEditAdminSettings()); fullExt.setUnmappedObjects(e.getUnmappedObjects()); return new Group(getWrapped(), fullExt); @@ -467,7 +494,13 @@ public Group editTopic(String topic) { e.isAsyncMembers(), e.canViewMembers(), e.canInvitePeople(), - e.isSharedHistory()); + e.isSharedHistory(), + e.canEditGroupInfo(), + e.getShortName(), + e.canEditShortName(), + e.canEditAdminList(), + e.canViewAdminList(), + e.canEditAdminSettings()); fullExt.setUnmappedObjects(e.getUnmappedObjects()); return new Group(getWrapped(), fullExt); } else { @@ -488,7 +521,40 @@ public Group editAbout(String about) { e.isAsyncMembers(), e.canViewMembers(), e.canInvitePeople(), - e.isSharedHistory()); + e.isSharedHistory(), + e.canEditGroupInfo(), + e.getShortName(), + e.canEditShortName(), + e.canEditAdminList(), + e.canViewAdminList(), + e.canEditAdminSettings()); + fullExt.setUnmappedObjects(e.getUnmappedObjects()); + return new Group(getWrapped(), fullExt); + } else { + return this; + } + } + + public Group editShortName(String shortName) { + if (getWrappedExt() != null) { + ApiGroupFull e = getWrappedExt(); + ApiGroupFull fullExt = new ApiGroupFull(e.getId(), + e.getCreateDate(), + e.getOwnerUid(), + e.getMembers(), + e.getTheme(), + e.getAbout(), + e.getExt(), + e.isAsyncMembers(), + e.canViewMembers(), + e.canInvitePeople(), + e.isSharedHistory(), + e.canEditGroupInfo(), + shortName, + e.canEditShortName(), + e.canEditAdminList(), + e.canViewAdminList(), + e.canEditAdminSettings()); fullExt.setUnmappedObjects(e.getUnmappedObjects()); return new Group(getWrapped(), fullExt); } else { @@ -509,7 +575,13 @@ public Group editFullExt(ApiMapValue ext) { e.isAsyncMembers(), e.canViewMembers(), e.canInvitePeople(), - e.isSharedHistory()); + e.isSharedHistory(), + e.canEditGroupInfo(), + e.getShortName(), + e.canEditShortName(), + e.canEditAdminList(), + e.canViewAdminList(), + e.canEditAdminSettings()); fullExt.setUnmappedObjects(e.getUnmappedObjects()); return new Group(getWrapped(), fullExt); } else { @@ -530,7 +602,148 @@ public Group editCanViewMembers(boolean canViewMembers) { e.isAsyncMembers(), canViewMembers, e.canInvitePeople(), - e.isSharedHistory()); + e.isSharedHistory(), + e.canEditGroupInfo(), + e.getShortName(), + e.canEditShortName(), + e.canEditAdminList(), + e.canViewAdminList(), + e.canEditAdminSettings()); + fullExt.setUnmappedObjects(e.getUnmappedObjects()); + return new Group(getWrapped(), fullExt); + } else { + return this; + } + } + + public Group editCanEditGroupInfo(boolean canEditGroupInfo) { + if (getWrappedExt() != null) { + ApiGroupFull e = getWrappedExt(); + ApiGroupFull fullExt = new ApiGroupFull(e.getId(), + e.getCreateDate(), + e.getOwnerUid(), + e.getMembers(), + e.getTheme(), + e.getAbout(), + e.getExt(), + e.isAsyncMembers(), + e.canViewMembers(), + e.canInvitePeople(), + e.isSharedHistory(), + canEditGroupInfo, + e.getShortName(), + e.canEditShortName(), + e.canEditAdminList(), + e.canViewAdminList(), + e.canEditAdminSettings()); + fullExt.setUnmappedObjects(e.getUnmappedObjects()); + return new Group(getWrapped(), fullExt); + } else { + return this; + } + } + + public Group editCanEditShortName(boolean canEditShortName) { + if (getWrappedExt() != null) { + ApiGroupFull e = getWrappedExt(); + ApiGroupFull fullExt = new ApiGroupFull(e.getId(), + e.getCreateDate(), + e.getOwnerUid(), + e.getMembers(), + e.getTheme(), + e.getAbout(), + e.getExt(), + e.isAsyncMembers(), + e.canViewMembers(), + e.canInvitePeople(), + e.isSharedHistory(), + e.canEditGroupInfo(), + e.getShortName(), + canEditShortName, + e.canEditAdminList(), + e.canViewAdminList(), + e.canEditAdminSettings()); + fullExt.setUnmappedObjects(e.getUnmappedObjects()); + return new Group(getWrapped(), fullExt); + } else { + return this; + } + } + + public Group editCanEditAdminList(boolean canEditAdminList) { + if (getWrappedExt() != null) { + ApiGroupFull e = getWrappedExt(); + ApiGroupFull fullExt = new ApiGroupFull(e.getId(), + e.getCreateDate(), + e.getOwnerUid(), + e.getMembers(), + e.getTheme(), + e.getAbout(), + e.getExt(), + e.isAsyncMembers(), + e.canViewMembers(), + e.canInvitePeople(), + e.isSharedHistory(), + e.canEditGroupInfo(), + e.getShortName(), + e.canEditShortName(), + canEditAdminList, + e.canViewAdminList(), + e.canEditAdminSettings()); + fullExt.setUnmappedObjects(e.getUnmappedObjects()); + return new Group(getWrapped(), fullExt); + } else { + return this; + } + } + + public Group editCanViewAdminList(boolean canViewAdminList) { + if (getWrappedExt() != null) { + ApiGroupFull e = getWrappedExt(); + ApiGroupFull fullExt = new ApiGroupFull(e.getId(), + e.getCreateDate(), + e.getOwnerUid(), + e.getMembers(), + e.getTheme(), + e.getAbout(), + e.getExt(), + e.isAsyncMembers(), + e.canViewMembers(), + e.canInvitePeople(), + e.isSharedHistory(), + e.canEditGroupInfo(), + e.getShortName(), + e.canEditShortName(), + e.canEditAdminList(), + canViewAdminList, + e.canEditAdminSettings()); + fullExt.setUnmappedObjects(e.getUnmappedObjects()); + return new Group(getWrapped(), fullExt); + } else { + return this; + } + } + + public Group editCanEditAdminSettings(boolean canEditAdminSettings) { + if (getWrappedExt() != null) { + ApiGroupFull e = getWrappedExt(); + ApiGroupFull fullExt = new ApiGroupFull(e.getId(), + e.getCreateDate(), + e.getOwnerUid(), + e.getMembers(), + e.getTheme(), + e.getAbout(), + e.getExt(), + e.isAsyncMembers(), + e.canViewMembers(), + e.canInvitePeople(), + e.isSharedHistory(), + e.canEditGroupInfo(), + e.getShortName(), + e.canEditShortName(), + e.canEditAdminList(), + e.canViewAdminList(), + canEditAdminSettings); fullExt.setUnmappedObjects(e.getUnmappedObjects()); return new Group(getWrapped(), fullExt); } else { @@ -551,7 +764,13 @@ public Group editCanInviteMembers(boolean canInviteMembers) { e.isAsyncMembers(), e.canViewMembers(), canInviteMembers, - e.isSharedHistory()); + e.isSharedHistory(), + e.canEditGroupInfo(), + e.getShortName(), + e.canEditShortName(), + e.canEditAdminList(), + e.canViewAdminList(), + e.canEditAdminSettings()); fullExt.setUnmappedObjects(e.getUnmappedObjects()); return new Group(getWrapped(), fullExt); } else { @@ -572,7 +791,13 @@ public Group editOwner(int uid) { e.isAsyncMembers(), e.canViewMembers(), e.canInvitePeople(), - e.isSharedHistory()); + e.isSharedHistory(), + e.canEditGroupInfo(), + e.getShortName(), + e.canEditShortName(), + e.canEditAdminList(), + e.canViewAdminList(), + e.canEditAdminSettings()); fullExt.setUnmappedObjects(e.getUnmappedObjects()); return new Group(getWrapped(), fullExt); } else { @@ -593,7 +818,13 @@ public Group editHistoryShared() { e.isAsyncMembers(), e.canViewMembers(), e.canInvitePeople(), - true); + true, + e.canEditGroupInfo(), + e.getShortName(), + e.canEditShortName(), + e.canEditAdminList(), + e.canViewAdminList(), + e.canEditAdminSettings()); fullExt.setUnmappedObjects(e.getUnmappedObjects()); return new Group(getWrapped(), fullExt); } else { @@ -610,7 +841,7 @@ protected void applyWrapped(@NotNull ApiGroup wrapped, @Nullable ApiGroupFull ex this.title = wrapped.getTitle(); this.avatar = wrapped.getAvatar() != null ? new Avatar(wrapped.getAvatar()) : null; this.isHidden = wrapped.isHidden() != null ? wrapped.isHidden() : false; - this.membersCount = wrapped.getMembersCount(); + this.membersCount = wrapped.getMembersCount() != null ? wrapped.getMembersCount() : 0; this.isMember = wrapped.isMember() != null ? wrapped.isMember() : true; if (wrapped.getGroupType() == null) { @@ -650,6 +881,7 @@ protected void applyWrapped(@NotNull ApiGroup wrapped, @Nullable ApiGroupFull ex this.isCanViewMembers = ext.canViewMembers() != null ? ext.canViewMembers() : true; this.isCanInviteMembers = ext.canViewMembers() != null ? ext.canViewMembers() : true; this.isSharedHistory = ext.isSharedHistory() != null ? ext.isSharedHistory() : false; + this.isCanEditInfo = ext.canEditGroupInfo() != null ? ext.canEditGroupInfo() : false; this.members = new ArrayList<>(); for (ApiMember m : ext.getMembers()) { this.members.add(new GroupMember(m.getUid(), m.getInviterUid(), m.getDate(), @@ -665,6 +897,7 @@ protected void applyWrapped(@NotNull ApiGroup wrapped, @Nullable ApiGroupFull ex this.isCanInviteMembers = false; this.isSharedHistory = false; this.members = new ArrayList<>(); + this.isCanEditInfo = false; } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsModule.java index dfa281d64c..5fe68cc5d5 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsModule.java @@ -192,7 +192,7 @@ public Promise transferOwnership(final int gid, final int uid) { return Promises.tuple(getGroups().getValueAsync(gid), users().getValueAsync(uid)) .flatMap(groupUserTuple2 -> api(new RequestTransferOwnership( new ApiGroupOutPeer(gid, groupUserTuple2.getT1().getAccessHash()), - uid))) + new ApiUserOutPeer(uid, groupUserTuple2.getT2().getAccessHash())))) .flatMap(r -> updates().waitForUpdate(r.getSeq())); } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsProcessor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsProcessor.java index 559cb12adf..bbb2f41700 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsProcessor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsProcessor.java @@ -6,8 +6,12 @@ import im.actor.core.api.updates.UpdateGroupAboutChanged; import im.actor.core.api.updates.UpdateGroupAvatarChanged; +import im.actor.core.api.updates.UpdateGroupCanEditAdminsChanged; +import im.actor.core.api.updates.UpdateGroupCanEditInfoChanged; +import im.actor.core.api.updates.UpdateGroupCanEditUsernameChanged; import im.actor.core.api.updates.UpdateGroupCanInviteMembersChanged; import im.actor.core.api.updates.UpdateGroupCanSendMessagesChanged; +import im.actor.core.api.updates.UpdateGroupCanViewAdminsChanged; import im.actor.core.api.updates.UpdateGroupCanViewMembersChanged; import im.actor.core.api.updates.UpdateGroupExtChanged; import im.actor.core.api.updates.UpdateGroupFullExtChanged; @@ -19,6 +23,7 @@ import im.actor.core.api.updates.UpdateGroupMembersCountChanged; import im.actor.core.api.updates.UpdateGroupMembersUpdated; import im.actor.core.api.updates.UpdateGroupOwnerChanged; +import im.actor.core.api.updates.UpdateGroupShortNameChanged; import im.actor.core.api.updates.UpdateGroupTitleChanged; import im.actor.core.api.updates.UpdateGroupTopicChanged; import im.actor.core.modules.AbsModule; @@ -48,13 +53,19 @@ public Promise process(Update update) { update instanceof UpdateGroupMembersBecameAsync || update instanceof UpdateGroupMembersCountChanged || + update instanceof UpdateGroupShortNameChanged || update instanceof UpdateGroupAboutChanged || update instanceof UpdateGroupTopicChanged || update instanceof UpdateGroupFullExtChanged || update instanceof UpdateGroupOwnerChanged || + update instanceof UpdateGroupHistoryShared || + update instanceof UpdateGroupCanViewMembersChanged || update instanceof UpdateGroupCanInviteMembersChanged || - update instanceof UpdateGroupHistoryShared) { + update instanceof UpdateGroupCanEditInfoChanged || + update instanceof UpdateGroupCanViewAdminsChanged || + update instanceof UpdateGroupCanEditAdminsChanged || + update instanceof UpdateGroupCanEditUsernameChanged) { return context().getGroupsModule().getRouter().onUpdate(update); } return null; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/router/GroupRouter.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/router/GroupRouter.java index 8708c67d43..1bf10431c3 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/router/GroupRouter.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/router/GroupRouter.java @@ -14,8 +14,13 @@ import im.actor.core.api.rpc.RequestLoadFullGroups; import im.actor.core.api.updates.UpdateGroupAboutChanged; import im.actor.core.api.updates.UpdateGroupAvatarChanged; +import im.actor.core.api.updates.UpdateGroupCanEditAdminSettingsChanged; +import im.actor.core.api.updates.UpdateGroupCanEditAdminsChanged; +import im.actor.core.api.updates.UpdateGroupCanEditInfoChanged; +import im.actor.core.api.updates.UpdateGroupCanEditUsernameChanged; import im.actor.core.api.updates.UpdateGroupCanInviteMembersChanged; import im.actor.core.api.updates.UpdateGroupCanSendMessagesChanged; +import im.actor.core.api.updates.UpdateGroupCanViewAdminsChanged; import im.actor.core.api.updates.UpdateGroupCanViewMembersChanged; import im.actor.core.api.updates.UpdateGroupExtChanged; import im.actor.core.api.updates.UpdateGroupFullExtChanged; @@ -27,6 +32,7 @@ import im.actor.core.api.updates.UpdateGroupMembersCountChanged; import im.actor.core.api.updates.UpdateGroupMembersUpdated; import im.actor.core.api.updates.UpdateGroupOwnerChanged; +import im.actor.core.api.updates.UpdateGroupShortNameChanged; import im.actor.core.api.updates.UpdateGroupTitleChanged; import im.actor.core.api.updates.UpdateGroupTopicChanged; import im.actor.core.entity.Group; @@ -129,6 +135,11 @@ public Promise onAboutChanged(int groupId, String about) { return editGroup(groupId, group -> group.editAbout(about)); } + @Verified + public Promise onShortNameChanged(int groupId, String shortName) { + return editGroup(groupId, group -> group.editShortName(shortName)); + } + @Verified public Promise onFullExtChanged(int groupId, ApiMapValue ext) { return editGroup(groupId, group -> group.editFullExt(ext)); @@ -144,6 +155,31 @@ public Promise onEditCanInviteMembers(int groupId, boolean canViewMembers) return editGroup(groupId, group -> group.editCanInviteMembers(canViewMembers)); } + @Verified + public Promise onEditCanEditGroupInfo(int groupId, boolean canEditGroupInfo) { + return editGroup(groupId, group -> group.editCanEditGroupInfo(canEditGroupInfo)); + } + + @Verified + public Promise onEditCanEditShortName(int groupId, boolean canEditShortName) { + return editGroup(groupId, group -> group.editCanEditShortName(canEditShortName)); + } + + @Verified + public Promise onEditCanEditAdminList(int groupId, boolean canEditAminList) { + return editGroup(groupId, group -> group.editCanEditAdminList(canEditAminList)); + } + + @Verified + public Promise onEditCanViewAdminList(int groupId, boolean canViewAdminList) { + return editGroup(groupId, group -> group.editCanViewAdminList(canViewAdminList)); + } + + @Verified + public Promise onEditCanEditAdminSettings(int groupId, boolean canEditAdminSettings) { + return editGroup(groupId, group -> group.editCanEditAdminSettings(canEditAdminSettings)); + } + @Verified public Promise onOwnerChanged(int groupId, int updatedOwner) { return editGroup(groupId, group -> group.editOwner(updatedOwner)); @@ -287,9 +323,6 @@ private Promise onUpdate(Update update) { } else if (update instanceof UpdateGroupAvatarChanged) { UpdateGroupAvatarChanged avatarChanged = (UpdateGroupAvatarChanged) update; return onAvatarChanged(avatarChanged.getGroupId(), avatarChanged.getAvatar()); - } else if (update instanceof UpdateGroupCanSendMessagesChanged) { - UpdateGroupCanSendMessagesChanged messagesChanged = (UpdateGroupCanSendMessagesChanged) update; - return onCanWriteMessagesChanged(messagesChanged.getGroupId(), messagesChanged.canSendMessages()); } else if (update instanceof UpdateGroupMemberChanged) { UpdateGroupMemberChanged memberChanged = (UpdateGroupMemberChanged) update; return onIsMemberChanged(memberChanged.getGroupId(), memberChanged.isMember()); @@ -337,15 +370,42 @@ else if (update instanceof UpdateGroupTopicChanged) { } else if (update instanceof UpdateGroupOwnerChanged) { UpdateGroupOwnerChanged ownerChanged = (UpdateGroupOwnerChanged) update; return onOwnerChanged(ownerChanged.getGroupId(), ownerChanged.getUserId()); + } else if (update instanceof UpdateGroupFullExtChanged) { + UpdateGroupFullExtChanged extChanged = (UpdateGroupFullExtChanged) update; + return onFullExtChanged(extChanged.getGroupId(), extChanged.getExt()); + } else if (update instanceof UpdateGroupShortNameChanged) { + UpdateGroupShortNameChanged shortNameChanged = (UpdateGroupShortNameChanged) update; + return onShortNameChanged(shortNameChanged.getGroupId(), shortNameChanged.getShortName()); + } + + // + // Actions + // + + else if (update instanceof UpdateGroupCanSendMessagesChanged) { + UpdateGroupCanSendMessagesChanged messagesChanged = (UpdateGroupCanSendMessagesChanged) update; + return onCanWriteMessagesChanged(messagesChanged.getGroupId(), messagesChanged.canSendMessages()); } else if (update instanceof UpdateGroupCanViewMembersChanged) { UpdateGroupCanViewMembersChanged membersChanged = (UpdateGroupCanViewMembersChanged) update; return onEditCanViewMembers(membersChanged.getGroupId(), membersChanged.canViewMembers()); } else if (update instanceof UpdateGroupCanInviteMembersChanged) { UpdateGroupCanInviteMembersChanged changed = (UpdateGroupCanInviteMembersChanged) update; return onEditCanInviteMembers(changed.getGroupId(), changed.canInviteMembers()); - } else if (update instanceof UpdateGroupFullExtChanged) { - UpdateGroupFullExtChanged extChanged = (UpdateGroupFullExtChanged) update; - return onFullExtChanged(extChanged.getGroupId(), extChanged.getExt()); + } else if (update instanceof UpdateGroupCanEditInfoChanged) { + UpdateGroupCanEditInfoChanged editInfoChanged = (UpdateGroupCanEditInfoChanged) update; + return onEditCanEditGroupInfo(editInfoChanged.getGroupId(), editInfoChanged.canEditGroup()); + } else if (update instanceof UpdateGroupCanEditUsernameChanged) { + UpdateGroupCanEditUsernameChanged shortName = (UpdateGroupCanEditUsernameChanged) update; + return onEditCanEditShortName(shortName.getGroupId(), shortName.canEditUsername()); + } else if (update instanceof UpdateGroupCanViewAdminsChanged) { + UpdateGroupCanViewAdminsChanged changed = (UpdateGroupCanViewAdminsChanged) update; + return onEditCanViewAdminList(changed.getGroupId(), changed.canViewAdmins()); + } else if (update instanceof UpdateGroupCanEditAdminsChanged) { + UpdateGroupCanEditAdminsChanged editAdmins = (UpdateGroupCanEditAdminsChanged) update; + return onEditCanEditAdminList(editAdmins.getGroupId(), editAdmins.canAssignAdmins()); + } else if (update instanceof UpdateGroupCanEditAdminSettingsChanged) { + UpdateGroupCanEditAdminSettingsChanged settings = (UpdateGroupCanEditAdminSettingsChanged) update; + return onEditCanEditAdminSettings(settings.getGroupId(), settings.canEditAdminSettings()); } return Promise.success(null); From f40473a73f46d2ced7ff634b64ecfbe917d63df1 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Fri, 15 Jul 2016 16:31:08 +0300 Subject: [PATCH 039/414] feat(core): Passed new Group fields to GroupVM --- .../main/java/im/actor/core/entity/Group.java | 14 ++ .../java/im/actor/core/viewmodel/GroupVM.java | 157 +++++++++++------- 2 files changed, 111 insertions(+), 60 deletions(-) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Group.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Group.java index 13d836feff..84f5c7f899 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Group.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Group.java @@ -70,6 +70,9 @@ public class Group extends WrapperExtEntity implements K @Nullable @Property("readonly, nonatomic") private String about; + @Nullable + @Property("readonly, nonatomic") + private String shortName; @NotNull @Property("readonly, nonatomic") @SuppressWarnings("NullableProblems") @@ -166,6 +169,11 @@ public String getAbout() { return about; } + @Nullable + public String getShortName() { + return shortName; + } + @Nullable public Integer getOwnerId() { return ownerId; @@ -183,6 +191,10 @@ public boolean isCanViewMembers() { return isCanViewMembers; } + public boolean isCanEditInfo() { + return isCanEditInfo; + } + public boolean isSharedHistory() { return isSharedHistory; } @@ -877,6 +889,7 @@ protected void applyWrapped(@NotNull ApiGroup wrapped, @Nullable ApiGroupFull ex this.ownerId = ext.getOwnerUid(); this.about = ext.getAbout(); this.topic = ext.getTheme(); + this.shortName = ext.getShortName(); this.isAsyncMembers = ext.isAsyncMembers() != null ? ext.isAsyncMembers() : false; this.isCanViewMembers = ext.canViewMembers() != null ? ext.canViewMembers() : true; this.isCanInviteMembers = ext.canViewMembers() != null ? ext.canViewMembers() : true; @@ -898,6 +911,7 @@ protected void applyWrapped(@NotNull ApiGroup wrapped, @Nullable ApiGroupFull ex this.isSharedHistory = false; this.members = new ArrayList<>(); this.isCanEditInfo = false; + this.shortName = null; } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java index d2c0d12d62..f60c3bb1f1 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java @@ -59,23 +59,32 @@ public class GroupVM extends BaseValueModel { private ValueModel> members; @NotNull @Property("nonatomic, readonly") + private BooleanValueModel isAsyncMembers; + @NotNull + @Property("nonatomic, readonly") private BooleanValueModel isCanViewMembers; @NotNull @Property("nonatomic, readonly") private BooleanValueModel isCanInviteMembers; + @NotNull + @Property("nonatomic, readonly") + private BooleanValueModel isCanEditInfo; + @NotNull @Property("nonatomic, readonly") - private IntValueModel ownerId; + private StringValueModel theme; @NotNull @Property("nonatomic, readonly") - private ValueModel presence; + private StringValueModel about; @NotNull @Property("nonatomic, readonly") - private StringValueModel theme; + private StringValueModel shortName; + @Property("nonatomic, readonly") + private IntValueModel ownerId; @NotNull @Property("nonatomic, readonly") - private StringValueModel about; + private ValueModel presence; @NotNull private ArrayList> listeners = new ArrayList<>(); @@ -98,11 +107,14 @@ public GroupVM(@NotNull Group rawObj) { this.isCanViewMembers = new BooleanValueModel("group." + groupId + ".can_view_members", rawObj.isCanViewMembers()); this.isCanInviteMembers = new BooleanValueModel("group." + groupId + ".can_invite_members", rawObj.isCanInviteMembers()); + this.isCanEditInfo = new BooleanValueModel("group." + groupId + ".can_edit_info", rawObj.isCanEditInfo()); + this.isAsyncMembers = new BooleanValueModel("group." + groupId + ".isAsyncMembers", rawObj.isAsyncMembers()); this.ownerId = new IntValueModel("group." + groupId + ".membersCount", rawObj.getOwnerId()); this.members = new ValueModel<>("group." + groupId + ".members", new HashSet<>(rawObj.getMembers())); this.presence = new ValueModel<>("group." + groupId + ".presence", 0); this.theme = new StringValueModel("group." + groupId + ".theme", rawObj.getTopic()); this.about = new StringValueModel("group." + groupId + ".about", rawObj.getAbout()); + this.shortName = new StringValueModel("group." + groupId + ".shortname", rawObj.getShortName()); } /** @@ -148,6 +160,28 @@ public AvatarValueModel getAvatar() { return avatar; } + /** + * Get About Value Model + * + * @return Value Model of String + */ + @NotNull + @ObjectiveCName("getAboutModel") + public StringValueModel getAbout() { + return about; + } + + /** + * Get Theme Value Model + * + * @return Value Model of String + */ + @NotNull + @ObjectiveCName("getThemeModel") + public StringValueModel getTheme() { + return theme; + } + /** * Get membership Value Model * @@ -192,6 +226,17 @@ public BooleanValueModel getIsCanViewMembers() { return isCanViewMembers; } + /** + * Can current user edit group info + * + * @return can edit group info + */ + @NotNull + @ObjectiveCName("isCanEditInfoModel") + public BooleanValueModel getIsCanEditInfo() { + return isCanEditInfo; + } + /** * Can current user invite members to a group * @@ -203,6 +248,18 @@ public BooleanValueModel getIsCanInviteMembers() { return isCanInviteMembers; } + + /** + * Is members should be fetched async + * + * @return is members async model + */ + @NotNull + @ObjectiveCName("getIsAsyncMembersModel") + public BooleanValueModel getIsAsyncMembers() { + return isAsyncMembers; + } + /** * Get Group owner user id model * @@ -235,47 +292,6 @@ public ValueModel getPresence() { return presence; } - @Override - protected void updateValues(@NotNull Group rawObj) { - boolean isChanged = name.change(rawObj.getTitle()); - - isChanged |= avatar.change(rawObj.getAvatar()); - isChanged |= membersCount.change(rawObj.getMembersCount()); - isChanged |= isMember.change(rawObj.isMember()); - isChanged |= isCanWriteMessage.change(rawObj.isCanWrite()); - - isChanged |= theme.change(rawObj.getTopic()); - isChanged |= about.change(rawObj.getAbout()); - isChanged |= members.change(new HashSet<>(rawObj.getMembers())); - isChanged |= ownerId.change(rawObj.getOwnerId()); - isChanged |= isCanViewMembers.change(rawObj.isCanViewMembers()); - - if (isChanged) { - notifyChange(); - } - } - - /** - * Get About Value Model - * - * @return Value Model of String - */ - @NotNull - @ObjectiveCName("getAboutModel") - public StringValueModel getAbout() { - return about; - } - - /** - * Get Theme Value Model - * - * @return Value Model of String - */ - @NotNull - @ObjectiveCName("getThemeModel") - public StringValueModel getTheme() { - return theme; - } /** * Subscribe for GroupVM updates @@ -284,8 +300,7 @@ public StringValueModel getTheme() { */ @MainThread @ObjectiveCName("subscribeWithListener:") - public void subscribe(@NotNull ModelChangedListener listener) { - // im.actor.runtime.Runtime.checkMainThread(); + public synchronized void subscribe(@NotNull ModelChangedListener listener) { if (listeners.contains(listener)) { return; } @@ -300,8 +315,7 @@ public void subscribe(@NotNull ModelChangedListener listener) { */ @MainThread @ObjectiveCName("subscribeWithListener:withNotify:") - public void subscribe(@NotNull ModelChangedListener listener, boolean notify) { - // im.actor.runtime.Runtime.checkMainThread(); + public synchronized void subscribe(@NotNull ModelChangedListener listener, boolean notify) { if (listeners.contains(listener)) { return; } @@ -318,11 +332,43 @@ public void subscribe(@NotNull ModelChangedListener listener, boolean n */ @MainThread @ObjectiveCName("unsubscribeWithListener:") - public void unsubscribe(@NotNull ModelChangedListener listener) { - // im.actor.runtime.Runtime.checkMainThread(); + public synchronized void unsubscribe(@NotNull ModelChangedListener listener) { listeners.remove(listener); } + // + // Update handling + // + + @Override + protected void updateValues(@NotNull Group rawObj) { + boolean isChanged = name.change(rawObj.getTitle()); + + isChanged |= avatar.change(rawObj.getAvatar()); + isChanged |= membersCount.change(rawObj.getMembersCount()); + isChanged |= isMember.change(rawObj.isMember()); + isChanged |= isCanWriteMessage.change(rawObj.isCanWrite()); + + isChanged |= theme.change(rawObj.getTopic()); + isChanged |= about.change(rawObj.getAbout()); + isChanged |= members.change(new HashSet<>(rawObj.getMembers())); + isChanged |= ownerId.change(rawObj.getOwnerId()); + isChanged |= isCanViewMembers.change(rawObj.isCanViewMembers()); + isChanged |= isCanEditInfo.change(rawObj.isCanEditInfo()); + isChanged |= shortName.change(rawObj.getShortName()); + isChanged |= isAsyncMembers.change(rawObj.isAsyncMembers()); + + if (isChanged) { + notifyIfNeeded(); + } + } + + private synchronized void notifyIfNeeded() { + if (listeners.size() > 0) { + notifyChange(); + } + } + private void notifyChange() { im.actor.runtime.Runtime.postToMainThread(() -> { for (ModelChangedListener l : listeners) { @@ -330,13 +376,4 @@ private void notifyChange() { } }); } - - private boolean isHaveMember(int uid, Collection members) { - for (GroupMember m : members) { - if (m.getUid() == uid) { - return true; - } - } - return false; - } } \ No newline at end of file From bcc8472293403c64fc263c4b1fd22d9a9f785046 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Fri, 15 Jul 2016 18:16:30 +0300 Subject: [PATCH 040/414] feat(android+core): Loading async members --- .../android-sdk/src/main/AndroidManifest.xml | 6 ++ .../src/main/java/im/actor/sdk/ActorSDK.java | 4 +- .../java/im/actor/sdk/ActorSDKDelegate.java | 13 ++-- .../im/actor/sdk/BaseActorSDKDelegate.java | 12 +-- .../actor/sdk/controllers/BaseFragment.java | 47 +++++++++++ .../controllers/group/GroupInfoActivity.java | 22 ++---- .../controllers/group/GroupInfoFragment.java | 31 ++++++-- .../controllers/group/MembersActivity.java | 18 +++++ .../controllers/group/MembersFragment.java | 78 +++++++++++++++++++ .../group/view/MembersAdapter.java | 30 ++++--- .../main/res/layout/fragment_group_header.xml | 18 +++++ .../src/main/res/layout/fragment_members.xml | 10 +++ .../main/java/im/actor/core/Messenger.java | 14 ++++ .../actor/core/entity/GroupMembersSlice.java | 22 ++++++ .../core/modules/groups/GroupsModule.java | 18 +++++ .../im/actor/runtime/promise/Promise.java | 43 +++++----- 16 files changed, 317 insertions(+), 69 deletions(-) create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/MembersActivity.java create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/MembersFragment.java create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_members.xml create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/GroupMembersSlice.java diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/AndroidManifest.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/AndroidManifest.xml index 799a9be9e8..dffc160e97 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/AndroidManifest.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/AndroidManifest.xml @@ -145,6 +145,12 @@ android:theme="@style/ProfileActivityTheme" android:windowSoftInputMode="adjustNothing|stateAlwaysHidden" /> + + pending = new ArrayList<>(); + public boolean isRootFragment() { return isRootFragment; } @@ -403,4 +412,42 @@ public ActionBar getSupportActionBar() { } return null; } + + protected Promise wrap(Promise p) { + WrappedPromise res = new WrappedPromise<>((PromiseFunc) resolver -> p.pipeTo(resolver)); + pending.add(res); + return res; + } + + @Override + public void onPause() { + super.onPause(); + + for (WrappedPromise w : pending) { + w.kill(); + } + pending.clear(); + } + + + private class WrappedPromise extends Promise { + + private boolean isKilled; + + public WrappedPromise(PromiseFunc executor) { + super(executor); + } + + public void kill() { + isKilled = true; + } + + @Override + protected void invokeDeliver() { + if (isKilled) { + return; + } + super.invokeDeliver(); + } + } } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoActivity.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoActivity.java index 5764a2a927..643b2135f7 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoActivity.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoActivity.java @@ -1,33 +1,25 @@ package im.actor.sdk.controllers.group; -import android.graphics.Color; -import android.graphics.drawable.ColorDrawable; import android.os.Bundle; +import android.support.v4.app.Fragment; import im.actor.sdk.ActorSDK; import im.actor.sdk.controllers.Intents; import im.actor.sdk.controllers.activity.BaseFragmentActivity; -import im.actor.sdk.controllers.settings.BaseGroupInfoActivity; public class GroupInfoActivity extends BaseFragmentActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - int chatId = getIntent().getIntExtra(Intents.EXTRA_GROUP_ID, 0); - - - + if (savedInstanceState == null) { - GroupInfoFragment fragment; - BaseGroupInfoActivity profileIntent = ActorSDK.sharedActor().getDelegate().getGroupInfoIntent(chatId); - if (profileIntent != null) { - fragment = profileIntent.getGroupInfoFragment(chatId); - } else { - fragment = GroupInfoFragment.create(chatId); + int groupId = getIntent().getIntExtra(Intents.EXTRA_GROUP_ID, 0); + Fragment profileIntent = ActorSDK.sharedActor().getDelegate().fragmentForGroupInfo(groupId); + if (profileIntent == null) { + profileIntent = GroupInfoFragment.create(groupId); } - - showFragment(fragment, false, false); + showFragment(profileIntent, false, false); } } } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java index efd92dc619..a43568d994 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java @@ -24,6 +24,7 @@ import com.google.i18n.phonenumbers.Phonenumber; import java.util.ArrayList; +import java.util.HashSet; import im.actor.core.entity.GroupMember; import im.actor.core.entity.GroupType; @@ -33,6 +34,8 @@ import im.actor.core.viewmodel.UserPhone; import im.actor.core.viewmodel.UserVM; import im.actor.runtime.actors.messages.Void; +import im.actor.runtime.mvvm.Value; +import im.actor.runtime.mvvm.ValueDoubleChangedListener; import im.actor.runtime.mvvm.ValueListener; import im.actor.sdk.ActorSDK; import im.actor.sdk.ActorSDKLauncher; @@ -115,6 +118,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa TextView aboutTV = (TextView) header.findViewById(R.id.about); View aboutCont = header.findViewById(R.id.aboutContainer); View addMemberCont = header.findViewById(R.id.addMemberCont); + View membersCont = header.findViewById(R.id.membersCont); View leaveCont = header.findViewById(R.id.leaveChannelCont); TextView title = (TextView) header.findViewById(R.id.title); @@ -142,6 +146,8 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa .setTextColor(style.getTextPrimaryColor()); ((TextView) header.findViewById(R.id.add_member_title)) .setTextColor(style.getTextPrimaryColor()); + ((TextView) header.findViewById(R.id.members_tite)) + .setTextColor(style.getTextPrimaryColor()); ((TextView) header.findViewById(R.id.leave_channel_title)) .setTextColor(style.getTextDangerColor()); @@ -242,19 +248,26 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa // Members - bind(groupVM.getIsCanViewMembers(), canViewMembers -> { + bind(groupVM.getIsCanViewMembers(), groupVM.getIsAsyncMembers(), (canViewMembers, vm1, isAsync, vm2) -> { if (canViewMembers) { - header.findViewById(R.id.after_settings_divider).setVisibility(View.VISIBLE); - // header.findViewById(R.id.membersHeader).setVisibility(View.VISIBLE); + if (isAsync) { + membersCont.setVisibility(View.VISIBLE); + header.findViewById(R.id.after_settings_divider).setVisibility(View.GONE); + } else { + membersCont.setVisibility(View.GONE); + header.findViewById(R.id.after_settings_divider).setVisibility(View.VISIBLE); + } } else { header.findViewById(R.id.after_settings_divider).setVisibility(View.GONE); - // header.findViewById(R.id.membersHeader).setVisibility(View.GONE); } }); + membersCont.setOnClickListener(view -> { + startActivity(new Intent(getContext(), MembersActivity.class) + .putExtra(Intents.EXTRA_GROUP_ID, groupVM.getId())); + }); listView.addHeaderView(header, null, false); - // // Footer // @@ -284,8 +297,12 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa // groupUserAdapter = new MembersAdapter(getActivity()); - bind(groupVM.getMembers(), members -> { - groupUserAdapter.setMembers(members); + bind(groupVM.getIsAsyncMembers(), groupVM.getMembers(), (isAsyncMembers, valueModel, members, valueModel2) -> { + if (isAsyncMembers) { + groupUserAdapter.setMembers(new ArrayList<>()); + } else { + groupUserAdapter.setMembers(members); + } }); listView.setAdapter(groupUserAdapter); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/MembersActivity.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/MembersActivity.java new file mode 100644 index 0000000000..b5d1278c15 --- /dev/null +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/MembersActivity.java @@ -0,0 +1,18 @@ +package im.actor.sdk.controllers.group; + +import android.os.Bundle; + +import im.actor.sdk.controllers.Intents; +import im.actor.sdk.controllers.activity.BaseFragmentActivity; + +public class MembersActivity extends BaseFragmentActivity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (savedInstanceState == null) { + int groupId = getIntent().getIntExtra(Intents.EXTRA_GROUP_ID, 0); + showFragment(MembersFragment.create(groupId), false, false); + } + } +} diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/MembersFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/MembersFragment.java new file mode 100644 index 0000000000..8e47223be3 --- /dev/null +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/MembersFragment.java @@ -0,0 +1,78 @@ +package im.actor.sdk.controllers.group; + +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ListView; + +import java.util.ArrayList; + +import im.actor.core.entity.GroupMember; +import im.actor.sdk.R; +import im.actor.sdk.controllers.BaseFragment; +import im.actor.sdk.controllers.group.view.MembersAdapter; + +import static im.actor.sdk.util.ActorSDKMessenger.messenger; + +public class MembersFragment extends BaseFragment { + + public static MembersFragment create(int groupId) { + MembersFragment res = new MembersFragment(); + Bundle args = new Bundle(); + args.putInt("groupId", groupId); + res.setArguments(args); + return res; + } + + private static final int LIMIT = 20; + + private int groupId; + private boolean isInitiallyLoaded; + private byte[] nextMembers; + private ArrayList members = new ArrayList<>(); + private MembersAdapter adapter; + + public MembersFragment() { + setRootFragment(true); + setTitle(R.string.group_members_header); + setHomeAsUp(true); + setShowHome(true); + } + + @Override + public void onCreate(Bundle saveInstance) { + super.onCreate(saveInstance); + + groupId = getArguments().getInt("groupId"); + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View res = inflater.inflate(R.layout.fragment_members, container, false); + adapter = new MembersAdapter(getContext()); + + ((ListView) res.findViewById(R.id.items)).setAdapter(adapter); + + return res; + } + + @Override + public void onResume() { + super.onResume(); + if (!isInitiallyLoaded) { + wrap(messenger().loadMembers(groupId, LIMIT, nextMembers)).then(groupMembersSlice -> { + isInitiallyLoaded = true; + members.addAll(groupMembersSlice.getUids()); + nextMembers = groupMembersSlice.getNext(); + ArrayList nMembers = new ArrayList<>(); + for (Integer uid : members) { + nMembers.add(new GroupMember(uid, 0, 0, false)); + } + adapter.setMembers(nMembers, false); + }); + } + } +} diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/view/MembersAdapter.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/view/MembersAdapter.java index a1180322ae..66976514a2 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/view/MembersAdapter.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/view/MembersAdapter.java @@ -32,19 +32,25 @@ public MembersAdapter(Context context) { } public void setMembers(Collection members) { + setMembers(members, true); + } + + public void setMembers(Collection members, boolean sort) { this.members = members.toArray(new GroupMember[members.size()]); - Arrays.sort(this.members, (a, b) -> { - if (a.isAdministrator() && !b.isAdministrator()) { - return -1; - } - if (b.isAdministrator() && !a.isAdministrator()) { - return 1; - } - String an = users().get(a.getInviterUid()).getName().get(); - String bn = users().get(b.getInviterUid()).getName().get(); - return an.compareTo(bn); - }); - notifyDataSetChanged(); + if (sort) { + Arrays.sort(this.members, (a, b) -> { + if (a.isAdministrator() && !b.isAdministrator()) { + return -1; + } + if (b.isAdministrator() && !a.isAdministrator()) { + return 1; + } + String an = users().get(a.getInviterUid()).getName().get(); + String bn = users().get(b.getInviterUid()).getName().get(); + return an.compareTo(bn); + }); + } + notifyDataSetInvalidated(); } @Override diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_group_header.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_group_header.xml index b3e31ebf6e..c8890a432f 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_group_header.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_group_header.xml @@ -148,6 +148,24 @@ android:layout_height="wrap_content" /> + + + + + + + + + + \ No newline at end of file diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java index f7934323c7..341d922809 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java @@ -21,6 +21,7 @@ import im.actor.core.entity.AuthStartRes; import im.actor.core.entity.FileReference; import im.actor.core.entity.Group; +import im.actor.core.entity.GroupMembersSlice; import im.actor.core.entity.MentionFilterResult; import im.actor.core.entity.MessageSearchEntity; import im.actor.core.entity.Peer; @@ -1523,6 +1524,19 @@ public Command kickMember(int gid, int uid) { .failure(e -> callback.onError(e)); } + /** + * Load async members + * + * @param gid group id + * @param limit limit of loading + * @param next optional cursor of next + * @return promise of members slice + */ + @ObjectiveCName("loadMembersWithGid:withLimit:withNext:") + public Promise loadMembers(int gid, int limit, byte[] next) { + return modules.getGroupsModule().loadMembers(gid, limit, next); + } + /** * Make member admin of group * diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/GroupMembersSlice.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/GroupMembersSlice.java new file mode 100644 index 0000000000..b8e0571eaa --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/GroupMembersSlice.java @@ -0,0 +1,22 @@ +package im.actor.core.entity; + +import java.util.ArrayList; + +public class GroupMembersSlice { + + private ArrayList uids; + private byte[] next; + + public GroupMembersSlice(ArrayList uids, byte[] next) { + this.uids = uids; + this.next = next; + } + + public ArrayList getUids() { + return uids; + } + + public byte[] getNext() { + return next; + } +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsModule.java index 5fe68cc5d5..2b5309f228 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsModule.java @@ -23,6 +23,7 @@ import im.actor.core.api.rpc.RequestJoinGroup; import im.actor.core.api.rpc.RequestKickUser; import im.actor.core.api.rpc.RequestLeaveGroup; +import im.actor.core.api.rpc.RequestLoadMembers; import im.actor.core.api.rpc.RequestMakeUserAdminObsolete; import im.actor.core.api.rpc.RequestRevokeIntegrationToken; import im.actor.core.api.rpc.RequestRevokeInviteUrl; @@ -30,6 +31,7 @@ import im.actor.core.api.rpc.ResponseIntegrationToken; import im.actor.core.api.rpc.ResponseInviteUrl; import im.actor.core.entity.Group; +import im.actor.core.entity.GroupMembersSlice; import im.actor.core.entity.Peer; import im.actor.core.entity.PeerType; import im.actor.core.entity.User; @@ -228,6 +230,22 @@ public Promise editAbout(final int gid, final String about) { .flatMap(r -> updates().waitForUpdate(r.getSeq())); } + public Promise loadMembers(int gid, int limit, byte[] next) { + return getGroups().getValueAsync(gid) + .flatMap(group -> + api(new RequestLoadMembers( + new ApiGroupOutPeer(group.getGroupId(), group.getAccessHash()), + limit, next))) + .chain(r -> updates().loadRequiredPeers(r.getMembers(), new ArrayList<>())) + .map(r -> { + ArrayList members = new ArrayList<>(); + for (ApiUserOutPeer p : r.getMembers()) { + members.add(p.getUid()); + } + return new GroupMembersSlice(members, r.getNext()); + }); + } + public void changeAvatar(int gid, String descriptor) { avatarChangeActor.send(new GroupAvatarChangeActor.ChangeAvatar(gid, descriptor)); } diff --git a/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/promise/Promise.java b/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/promise/Promise.java index 260cd1e5b1..88cf371185 100644 --- a/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/promise/Promise.java +++ b/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/promise/Promise.java @@ -223,31 +223,34 @@ synchronized void tryResult(@Nullable T res) { /** * Delivering result */ - private void deliverResult() { + protected void deliverResult() { if (callbacks.size() > 0) { dispatcher.dispatch(() -> { - if (exception != null) { - for (PromiseCallback callback : callbacks) { - try { - callback.onError(exception); - } catch (Exception e) { - e.printStackTrace(); - } - } - } else { - for (PromiseCallback callback : callbacks) { - try { - callback.onResult(result); - } catch (Exception e) { - e.printStackTrace(); - } - } - } - callbacks.clear(); + invokeDeliver(); }); } } - + + protected void invokeDeliver() { + if (exception != null) { + for (PromiseCallback callback : callbacks) { + try { + callback.onError(exception); + } catch (Exception e) { + e.printStackTrace(); + } + } + } else { + for (PromiseCallback callback : callbacks) { + try { + callback.onResult(result); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + callbacks.clear(); + } // // Conversions From a52fd79939e962594d26a0276cd8e54b2fb19a1c Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Fri, 15 Jul 2016 18:47:45 +0300 Subject: [PATCH 041/414] fix(core): Fixing not updating "isCanInviteMembers" --- .../src/main/java/im/actor/core/viewmodel/GroupVM.java | 1 + 1 file changed, 1 insertion(+) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java index f60c3bb1f1..0e8013f07c 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java @@ -354,6 +354,7 @@ protected void updateValues(@NotNull Group rawObj) { isChanged |= members.change(new HashSet<>(rawObj.getMembers())); isChanged |= ownerId.change(rawObj.getOwnerId()); isChanged |= isCanViewMembers.change(rawObj.isCanViewMembers()); + isChanged |= isCanInviteMembers.change(rawObj.isCanInviteMembers()); isChanged |= isCanEditInfo.change(rawObj.isCanEditInfo()); isChanged |= shortName.change(rawObj.getShortName()); isChanged |= isAsyncMembers.change(rawObj.isAsyncMembers()); From 228084cb4b629f9776ee137e2926ab86bb24baa3 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Sat, 16 Jul 2016 11:03:21 +0300 Subject: [PATCH 042/414] feat(android): Implemented channel and group creation --- .../controllers/compose/ComposeFragment.java | 10 +++- .../compose/CreateGroupActivity.java | 5 +- .../compose/GroupNameFragment.java | 51 ++++++++++++++++--- .../src/main/res/values/ui_text.xml | 6 +++ 4 files changed, 63 insertions(+), 9 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/compose/ComposeFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/compose/ComposeFragment.java index c116e82271..58b0245299 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/compose/ComposeFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/compose/ComposeFragment.java @@ -23,7 +23,15 @@ protected void addFootersAndHeaders() { addFooterOrHeaderAction(ActorSDK.sharedActor().style.getActionShareColor(), R.drawable.ic_group_white_24dp, R.string.main_fab_new_group, false, () -> { - startActivity(new Intent(getActivity(), CreateGroupActivity.class)); + startActivity(new Intent(getActivity(), CreateGroupActivity.class) + .putExtra(CreateGroupActivity.EXTRA_IS_CHANNEL, false)); + getActivity().finish(); + }, true); + + addFooterOrHeaderAction(ActorSDK.sharedActor().style.getActionShareColor(), + R.drawable.ic_megaphone_18dp_black, R.string.main_fab_new_channel, false, () -> { + startActivity(new Intent(getActivity(), CreateGroupActivity.class) + .putExtra(CreateGroupActivity.EXTRA_IS_CHANNEL, true)); getActivity().finish(); }, true); } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/compose/CreateGroupActivity.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/compose/CreateGroupActivity.java index 11e80ed94e..2cbca1be3a 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/compose/CreateGroupActivity.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/compose/CreateGroupActivity.java @@ -7,12 +7,15 @@ public class CreateGroupActivity extends BaseFragmentActivity { + public static String EXTRA_IS_CHANNEL = "is_channel"; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (savedInstanceState == null) { - showFragment(new GroupNameFragment(), false, false); + showFragment(new GroupNameFragment(getIntent().getBooleanExtra(EXTRA_IS_CHANNEL, false)), + false, false); } } } \ No newline at end of file diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/compose/GroupNameFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/compose/GroupNameFragment.java index 874554f49a..b53759dfa0 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/compose/GroupNameFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/compose/GroupNameFragment.java @@ -33,6 +33,8 @@ public class GroupNameFragment extends BaseFragment { private static final int REQUEST_AVATAR = 1; + private boolean isChannel; + private EditText groupName; private AvatarView avatarView; @@ -42,17 +44,43 @@ public class GroupNameFragment extends BaseFragment { public GroupNameFragment() { setRootFragment(true); - setTitle(R.string.create_group_title); setHomeAsUp(true); } + public GroupNameFragment(boolean isChannel) { + this(); + Bundle args = new Bundle(); + args.putBoolean("isChannel", isChannel); + setArguments(args); + } + + @Override + public void onCreate(Bundle saveInstance) { + super.onCreate(saveInstance); + this.isChannel = getArguments().getBoolean("isChannel"); + + if (isChannel) { + setTitle(R.string.create_channel_title); + } else { + setTitle(R.string.create_group_title); + } + } + @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { helper = new KeyboardHelper(getActivity()); View res = inflater.inflate(R.layout.fragment_create_group_name, container, false); res.setBackgroundColor(ActorSDK.sharedActor().style.getMainBackgroundColor()); - ((TextView) res.findViewById(R.id.create_group_hint)).setTextColor(ActorSDK.sharedActor().style.getTextSecondaryColor()); + + TextView hintTextView = (TextView) res.findViewById(R.id.create_group_hint); + hintTextView.setTextColor(ActorSDK.sharedActor().style.getTextSecondaryColor()); + if (isChannel) { + hintTextView.setText(R.string.create_channel_hint); + } else { + hintTextView.setText(R.string.create_group_hint); + } + groupName = (EditText) res.findViewById(R.id.groupTitle); groupName.setOnEditorActionListener((v, actionId, event) -> { if (actionId == EditorInfo.IME_ACTION_NEXT) { @@ -61,6 +89,11 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa } return false; }); + if (isChannel) { + groupName.setHint(R.string.create_channel_name_hint); + } else { + groupName.setHint(R.string.create_group_name_hint); + } groupName.setTextColor(ActorSDK.sharedActor().style.getTextPrimaryColor()); groupName.setHintTextColor(ActorSDK.sharedActor().style.getTextHintColor()); @@ -102,11 +135,15 @@ public boolean onOptionsItemSelected(MenuItem item) { private void next() { String title = groupName.getText().toString().trim(); if (title.length() > 0) { -// ((CreateGroupActivity) getActivity()).showNextFragment( -// GroupUsersFragment.create(groupName.getText().toString().trim(), avatarPath), false, true); - messenger().createChannel(groupName.getText().toString().trim(), avatarPath).then(gid -> { - getActivity().finish(); - }); + if (isChannel) { + execute(messenger().createChannel(groupName.getText().toString().trim(), avatarPath).then(gid -> { + startActivity(Intents.openGroupDialog(gid, false, getActivity())); + getActivity().finish(); + })); + } else { + ((CreateGroupActivity) getActivity()).showNextFragment( + GroupUsersFragment.create(groupName.getText().toString().trim(), avatarPath), false, true); + } } } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml index c1d0a4292c..f06802207d 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml @@ -158,6 +158,7 @@ Compose Create group + Create channel Join public group Add contact @@ -391,6 +392,11 @@ Search by name Done + + Create channel + Name the channel + Enter channel name and set optional channel picture + Add member From 087ee0677f2aff3f741022ea51ce84d54dc13e05 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Sat, 16 Jul 2016 11:03:40 +0300 Subject: [PATCH 043/414] feat(scheme): Updated send encrypted package method --- actor-sdk/sdk-api/actor.json | 82 ++++++++++--------- .../models/im/actor/api/scheme.mps | 69 ++++++++-------- 2 files changed, 82 insertions(+), 69 deletions(-) diff --git a/actor-sdk/sdk-api/actor.json b/actor-sdk/sdk-api/actor.json index 68e4ac4d01..241fe5e581 100644 --- a/actor-sdk/sdk-api/actor.json +++ b/actor-sdk/sdk-api/actor.json @@ -19626,7 +19626,7 @@ { "type": "reference", "argument": "keyGroupId", - "category": "hidden", + "category": "full", "description": " Key Group Id" } ], @@ -19650,6 +19650,45 @@ ] } }, + { + "type": "struct", + "content": { + "name": "KeyGroupHolder", + "doc": [ + "Key Group Holder", + { + "type": "reference", + "argument": "uid", + "category": "full", + "description": " User's id" + }, + { + "type": "reference", + "argument": "keyGroup", + "category": "full", + "description": " Key Group" + } + ], + "attributes": [ + { + "type": { + "type": "alias", + "childType": "userId" + }, + "id": 1, + "name": "uid" + }, + { + "type": { + "type": "struct", + "childType": "EncryptionKeyGroup" + }, + "id": 2, + "name": "keyGroup" + } + ] + } + }, { "type": "rpc", "content": { @@ -19659,18 +19698,6 @@ "type": "anonymous", "header": 2664, "doc": [ - { - "type": "reference", - "argument": "seq", - "category": "full", - "description": " seq" - }, - { - "type": "reference", - "argument": "state", - "category": "full", - "description": " state" - }, { "type": "reference", "argument": "date", @@ -19681,7 +19708,7 @@ "type": "reference", "argument": "obsoleteKeyGroups", "category": "full", - "description": " obsolete key groups" + "description": " obsolete key group ids" }, { "type": "reference", @@ -19691,25 +19718,6 @@ } ], "attributes": [ - { - "type": { - "type": "opt", - "childType": "int32" - }, - "id": 1, - "name": "seq" - }, - { - "type": { - "type": "opt", - "childType": { - "type": "alias", - "childType": "seq_state" - } - }, - "id": 2, - "name": "state" - }, { "type": { "type": "opt", @@ -19718,7 +19726,7 @@ "childType": "date" } }, - "id": 3, + "id": 1, "name": "date" }, { @@ -19729,7 +19737,7 @@ "childType": "KeyGroupId" } }, - "id": 4, + "id": 2, "name": "obsoleteKeyGroups" }, { @@ -19737,10 +19745,10 @@ "type": "list", "childType": { "type": "struct", - "childType": "KeyGroupId" + "childType": "KeyGroupHolder" } }, - "id": 5, + "id": 3, "name": "missedKeyGroups" } ] diff --git a/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps b/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps index e9ba693420..37130204ab 100644 --- a/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps +++ b/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps @@ -16629,9 +16629,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -16677,24 +16708,8 @@ - - - - - - - - - - - - - - - - - + @@ -16703,7 +16718,7 @@ - + @@ -16712,27 +16727,17 @@ - + - - + + - - - - - - - - - - @@ -16740,7 +16745,7 @@ - + From 133b067dcfd823ea0f6059e70cbc1997da5a54bd Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Sat, 16 Jul 2016 11:03:40 +0300 Subject: [PATCH 044/414] feat(scheme): Updated send encrypted package method --- actor-sdk/sdk-api/actor.json | 82 ++++++++++--------- .../models/im/actor/api/scheme.mps | 69 ++++++++-------- 2 files changed, 82 insertions(+), 69 deletions(-) diff --git a/actor-sdk/sdk-api/actor.json b/actor-sdk/sdk-api/actor.json index 7219e2b553..6246ebdfc2 100644 --- a/actor-sdk/sdk-api/actor.json +++ b/actor-sdk/sdk-api/actor.json @@ -19620,7 +19620,7 @@ { "type": "reference", "argument": "keyGroupId", - "category": "hidden", + "category": "full", "description": " Key Group Id" } ], @@ -19644,6 +19644,45 @@ ] } }, + { + "type": "struct", + "content": { + "name": "KeyGroupHolder", + "doc": [ + "Key Group Holder", + { + "type": "reference", + "argument": "uid", + "category": "full", + "description": " User's id" + }, + { + "type": "reference", + "argument": "keyGroup", + "category": "full", + "description": " Key Group" + } + ], + "attributes": [ + { + "type": { + "type": "alias", + "childType": "userId" + }, + "id": 1, + "name": "uid" + }, + { + "type": { + "type": "struct", + "childType": "EncryptionKeyGroup" + }, + "id": 2, + "name": "keyGroup" + } + ] + } + }, { "type": "rpc", "content": { @@ -19653,18 +19692,6 @@ "type": "anonymous", "header": 2664, "doc": [ - { - "type": "reference", - "argument": "seq", - "category": "full", - "description": " seq" - }, - { - "type": "reference", - "argument": "state", - "category": "full", - "description": " state" - }, { "type": "reference", "argument": "date", @@ -19675,7 +19702,7 @@ "type": "reference", "argument": "obsoleteKeyGroups", "category": "full", - "description": " obsolete key groups" + "description": " obsolete key group ids" }, { "type": "reference", @@ -19685,25 +19712,6 @@ } ], "attributes": [ - { - "type": { - "type": "opt", - "childType": "int32" - }, - "id": 1, - "name": "seq" - }, - { - "type": { - "type": "opt", - "childType": { - "type": "alias", - "childType": "seq_state" - } - }, - "id": 2, - "name": "state" - }, { "type": { "type": "opt", @@ -19712,7 +19720,7 @@ "childType": "date" } }, - "id": 3, + "id": 1, "name": "date" }, { @@ -19723,7 +19731,7 @@ "childType": "KeyGroupId" } }, - "id": 4, + "id": 2, "name": "obsoleteKeyGroups" }, { @@ -19731,10 +19739,10 @@ "type": "list", "childType": { "type": "struct", - "childType": "KeyGroupId" + "childType": "KeyGroupHolder" } }, - "id": 5, + "id": 3, "name": "missedKeyGroups" } ] diff --git a/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps b/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps index da8e3130f9..9af1d71b07 100644 --- a/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps +++ b/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps @@ -16629,9 +16629,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -16677,24 +16708,8 @@ - - - - - - - - - - - - - - - - - + @@ -16703,7 +16718,7 @@ - + @@ -16712,27 +16727,17 @@ - + - - + + - - - - - - - - - - @@ -16740,7 +16745,7 @@ - + From b0376881f143a8fbbd3ff176bcc7b966586071be Mon Sep 17 00:00:00 2001 From: unreg Date: Sun, 17 Jul 2016 16:40:11 +0300 Subject: [PATCH 045/414] Issue #278 resolved --- .../main/java/im/actor/runtime/markdown/MarkdownParser.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/markdown/MarkdownParser.java b/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/markdown/MarkdownParser.java index 9d8387453b..dcb0b6eac8 100644 --- a/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/markdown/MarkdownParser.java +++ b/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/markdown/MarkdownParser.java @@ -374,10 +374,10 @@ private BasicUrl findUrl(TextCursor cursor, int limit) { * @return is good anchor */ private boolean isGoodAnchor(String text, int index) { - // Check if there is space after block + // Check if there is space and punctuation mark after block + String punct = " .,:!?\t\n"; if (index >= 0 && index < text.length()) { - char postfix = text.charAt(index); - if (postfix != ' ' && postfix != '\t' && postfix != '\n') { + if (punct.indexOf(text.charAt(index)) == -1) { return false; } } From cc06340b021cf8f48da59a787aca91078568f8df Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Sun, 17 Jul 2016 17:41:51 +0300 Subject: [PATCH 046/414] feat(android): Group Info layout update --- .../main/java/im/actor/map/MapActivity.java | 2 +- .../im/actor/sdk/controllers/ActorBinder.java | 7 + .../sdk/controllers/BinderCompatFragment.java | 5 + .../activity/BaseFragmentActivity.java | 8 +- .../sdk/controllers/auth/AuthActivity.java | 10 +- .../controllers/auth/PickCountryActivity.java | 2 +- .../sdk/controllers/calls/CallActivity.java | 2 +- .../controllers/compose/ComposeActivity.java | 2 +- .../compose/CreateGroupActivity.java | 2 +- .../compose/GroupNameFragment.java | 2 +- .../contacts/ContactsActivity.java | 2 +- .../controllers/contacts/InviteActivity.java | 2 +- .../fragment/help/HelpActivity.java | 2 +- .../controllers/group/AddMemberActivity.java | 2 +- .../controllers/group/GroupInfoActivity.java | 2 +- .../group/GroupInfoEditActivity.java | 17 ++ .../group/GroupInfoEditFragment.java | 10 + .../controllers/group/GroupInfoFragment.java | 219 ++++++------------ .../controllers/group/InviteLinkActivity.java | 2 +- .../controllers/group/MembersActivity.java | 2 +- .../controllers/profile/ProfileActivity.java | 2 +- .../settings/BlockedListActivity.java | 2 +- .../settings/ChatSettingsActivity.java | 2 +- .../settings/EditAboutActivity.java | 2 +- .../settings/EditNameActivity.java | 2 +- .../settings/MyProfileActivity.java | 2 +- .../settings/NotificationsActivity.java | 2 +- .../settings/PickWallpaperActivity.java | 2 +- .../settings/SecuritySettingsActivity.java | 2 +- .../main/res/layout/fragment_group_header.xml | 93 ++------ .../src/main/res/menu/group_info.xml | 3 - .../runtime/mvvm/ValueDoubleListener.java | 9 + .../im/actor/runtime/mvvm/ValueListener.java | 1 + 33 files changed, 167 insertions(+), 259 deletions(-) create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoEditActivity.java create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoEditFragment.java create mode 100644 actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/mvvm/ValueDoubleListener.java diff --git a/actor-sdk/sdk-core-android/android-google-maps/src/main/java/im/actor/map/MapActivity.java b/actor-sdk/sdk-core-android/android-google-maps/src/main/java/im/actor/map/MapActivity.java index 6ed714b379..9e95b2b3bd 100644 --- a/actor-sdk/sdk-core-android/android-google-maps/src/main/java/im/actor/map/MapActivity.java +++ b/actor-sdk/sdk-core-android/android-google-maps/src/main/java/im/actor/map/MapActivity.java @@ -18,7 +18,7 @@ protected void onCreate(Bundle savedInstanceState) { double latitude = getIntent().getDoubleExtra("latitude", 0); if (savedInstanceState == null) { - showFragment(MapFragment.create(longitude, latitude), false, false); + showFragment(MapFragment.create(longitude, latitude), false); } } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/ActorBinder.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/ActorBinder.java index 72f1b7f15e..d65b31f22a 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/ActorBinder.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/ActorBinder.java @@ -14,6 +14,7 @@ import im.actor.core.viewmodel.GroupVM; import im.actor.core.viewmodel.UserPresence; import im.actor.core.viewmodel.UserVM; +import im.actor.runtime.mvvm.ValueDoubleListener; import im.actor.runtime.mvvm.ValueListener; import im.actor.runtime.mvvm.ValueModel; import im.actor.sdk.ActorSDK; @@ -236,6 +237,12 @@ public Binding bind(Value value, ValueListener listener) { }); } + public void bind(Value value1, Value value2, ValueDoubleListener listener) { + bind(value1, value2, (val, valueModel, val2, valueModel2) -> { + listener.onChanged(val, val2); + }); + } + public Binding bind(Value value, ValueChangedListener listener, boolean notify) { value.subscribe(listener, notify); Binding b = new Binding(value, listener); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/BinderCompatFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/BinderCompatFragment.java index 6e247a6940..f8a2f47987 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/BinderCompatFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/BinderCompatFragment.java @@ -8,6 +8,7 @@ import im.actor.core.viewmodel.UserVM; import im.actor.runtime.mvvm.Value; import im.actor.runtime.mvvm.ValueDoubleChangedListener; +import im.actor.runtime.mvvm.ValueDoubleListener; import im.actor.runtime.mvvm.ValueListener; import im.actor.sdk.view.avatar.AvatarView; import im.actor.sdk.view.avatar.CoverAvatarView; @@ -40,6 +41,10 @@ public void bind(ValueModel value, ValueListener listener) { BINDER.bind(value, listener); } + public void bind(Value value1, Value value2, ValueDoubleListener listener) { + BINDER.bind(value1, value2, listener); + } + public void bind(ValueModel value, boolean notify, ValueChangedListener listener) { BINDER.bind(value, listener, notify); } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/activity/BaseFragmentActivity.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/activity/BaseFragmentActivity.java index 9895722d16..5a88b798b2 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/activity/BaseFragmentActivity.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/activity/BaseFragmentActivity.java @@ -44,11 +44,7 @@ protected void onCreate(Bundle savedInstanceState) { getWindow().setBackgroundDrawable(new ColorDrawable(STYLE.getMainBackgroundColor())); } - public void showFragment(final Fragment fragment, final boolean addToBackStack) { - showFragment(fragment, addToBackStack, false); - } - - public void showFragment(final Fragment fragment, final boolean addToBackStack, final boolean isAnimated) { + public void showFragment(Fragment fragment, boolean addToBackStack) { FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); transaction.replace(R.id.content_frame, fragment); if (addToBackStack) { @@ -57,7 +53,7 @@ public void showFragment(final Fragment fragment, final boolean addToBackStack, transaction.commit(); } - public void showNextFragment(final Fragment fragment, final boolean addToBackStack, final boolean isAnimated) { + public void showNextFragment(Fragment fragment, boolean addToBackStack) { FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); transaction.replace(R.id.content_frame, fragment); if (addToBackStack) { diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/auth/AuthActivity.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/auth/AuthActivity.java index 558ca25c5b..c6cb930c8e 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/auth/AuthActivity.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/auth/AuthActivity.java @@ -123,7 +123,7 @@ private void updateState(AuthState state, boolean force) { if (signType == SIGN_TYPE_UP) { updateState(AuthState.SIGN_UP); } else if (signType == SIGN_TYPE_IN) { - showFragment(new SignInFragment(), false, false); + showFragment(new SignInFragment(), false); } break; @@ -131,18 +131,18 @@ private void updateState(AuthState state, boolean force) { if (currentName != null && !currentName.isEmpty()) { startAuth(currentName); } else { - showFragment(new SignUpFragment(), false, false); + showFragment(new SignUpFragment(), false); } break; case AUTH_PHONE: currentAuthType = AUTH_TYPE_PHONE; currentCode = ""; - showFragment(ActorSDK.sharedActor().getDelegatedFragment(ActorSDK.sharedActor().getDelegate().getAuthStartIntent(), new SignPhoneFragment(), BaseAuthFragment.class), false, false); + showFragment(ActorSDK.sharedActor().getDelegatedFragment(ActorSDK.sharedActor().getDelegate().getAuthStartIntent(), new SignPhoneFragment(), BaseAuthFragment.class), false); break; case AUTH_EMAIL: currentCode = ""; currentAuthType = AUTH_TYPE_EMAIL; - showFragment(ActorSDK.sharedActor().getDelegatedFragment(ActorSDK.sharedActor().getDelegate().getAuthStartIntent(), new SignEmailFragment(), BaseAuthFragment.class), false, false); + showFragment(ActorSDK.sharedActor().getDelegatedFragment(ActorSDK.sharedActor().getDelegate().getAuthStartIntent(), new SignEmailFragment(), BaseAuthFragment.class), false); break; case CODE_VALIDATION_PHONE: case CODE_VALIDATION_EMAIL: @@ -153,7 +153,7 @@ private void updateState(AuthState state, boolean force) { args.putBoolean(ValidateCodeFragment.AUTH_TYPE_SIGN, signType == SIGN_TYPE_IN); args.putString("authId", state == AuthState.CODE_VALIDATION_EMAIL ? currentEmail : Long.toString(currentPhone)); signInFragment.setArguments(args); - showFragment(signInFragment, false, false); + showFragment(signInFragment, false); break; case LOGGED_IN: finish(); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/auth/PickCountryActivity.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/auth/PickCountryActivity.java index 5ce842cd2f..049536c68a 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/auth/PickCountryActivity.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/auth/PickCountryActivity.java @@ -12,7 +12,7 @@ protected void onCreate(Bundle savedInstanceState) { getSupportActionBar().setTitle(R.string.auth_phone_country_title); if (savedInstanceState == null) { - showFragment(new PickCountryFragment(), false, false); + showFragment(new PickCountryFragment(), false); } } } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/calls/CallActivity.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/calls/CallActivity.java index 88c85b3522..d2c1f0a46e 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/calls/CallActivity.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/calls/CallActivity.java @@ -44,7 +44,7 @@ protected void onCreate(Bundle savedInstanceState) { if (savedInstanceState == null) { callId = getIntent().getLongExtra("callId", -1); - showFragment(new CallFragment(callId), false, false); + showFragment(new CallFragment(callId), false); } } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/compose/ComposeActivity.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/compose/ComposeActivity.java index fee494ed83..ce80f5e17b 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/compose/ComposeActivity.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/compose/ComposeActivity.java @@ -11,7 +11,7 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (savedInstanceState == null) { - showFragment(new ComposeFragment(), false, false); + showFragment(new ComposeFragment(), false); } } } \ No newline at end of file diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/compose/CreateGroupActivity.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/compose/CreateGroupActivity.java index 2cbca1be3a..1995ba5bc7 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/compose/CreateGroupActivity.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/compose/CreateGroupActivity.java @@ -15,7 +15,7 @@ protected void onCreate(Bundle savedInstanceState) { if (savedInstanceState == null) { showFragment(new GroupNameFragment(getIntent().getBooleanExtra(EXTRA_IS_CHANNEL, false)), - false, false); + false); } } } \ No newline at end of file diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/compose/GroupNameFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/compose/GroupNameFragment.java index b53759dfa0..9d8bea9a22 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/compose/GroupNameFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/compose/GroupNameFragment.java @@ -142,7 +142,7 @@ private void next() { })); } else { ((CreateGroupActivity) getActivity()).showNextFragment( - GroupUsersFragment.create(groupName.getText().toString().trim(), avatarPath), false, true); + GroupUsersFragment.create(groupName.getText().toString().trim(), avatarPath), false); } } } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/contacts/ContactsActivity.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/contacts/ContactsActivity.java index c8b46701d1..54de05d097 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/contacts/ContactsActivity.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/contacts/ContactsActivity.java @@ -12,7 +12,7 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (savedInstanceState == null) { - showFragment(new ContactsFragment(), false, false); + showFragment(new ContactsFragment(), false); } } } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/contacts/InviteActivity.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/contacts/InviteActivity.java index 52bb42a834..1fda1902a9 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/contacts/InviteActivity.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/contacts/InviteActivity.java @@ -17,7 +17,7 @@ protected void onCreate(Bundle savedInstanceState) { if (savedInstanceState == null) { - showFragment(new InviteFragment(), false, false); + showFragment(new InviteFragment(), false); } } } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/fragment/help/HelpActivity.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/fragment/help/HelpActivity.java index efbbcb3fa2..f931aea58f 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/fragment/help/HelpActivity.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/fragment/help/HelpActivity.java @@ -12,6 +12,6 @@ protected void onCreate(Bundle savedInstanceState) { getSupportActionBar().setTitle(R.string.help_title); - showFragment(new HelpFragment(), false, false); + showFragment(new HelpFragment(), false); } } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/AddMemberActivity.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/AddMemberActivity.java index e036571294..cd4defde71 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/AddMemberActivity.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/AddMemberActivity.java @@ -12,7 +12,7 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (savedInstanceState == null) { - showFragment(AddMemberFragment.create(getIntent().getIntExtra("GROUP_ID", 0)), false, false); + showFragment(AddMemberFragment.create(getIntent().getIntExtra("GROUP_ID", 0)), false); } } } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoActivity.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoActivity.java index 643b2135f7..be166a33c4 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoActivity.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoActivity.java @@ -19,7 +19,7 @@ protected void onCreate(Bundle savedInstanceState) { if (profileIntent == null) { profileIntent = GroupInfoFragment.create(groupId); } - showFragment(profileIntent, false, false); + showFragment(profileIntent, false); } } } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoEditActivity.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoEditActivity.java new file mode 100644 index 0000000000..c738f6c6c9 --- /dev/null +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoEditActivity.java @@ -0,0 +1,17 @@ +package im.actor.sdk.controllers.group; + +import android.os.Bundle; + +import im.actor.sdk.controllers.activity.BaseFragmentActivity; + +public class GroupInfoEditActivity extends BaseFragmentActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (savedInstanceState == null) { + showFragment(new GroupInfoEditFragment(), false); + } + } +} diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoEditFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoEditFragment.java new file mode 100644 index 0000000000..fd1d2012b1 --- /dev/null +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoEditFragment.java @@ -0,0 +1,10 @@ +package im.actor.sdk.controllers.group; + +import im.actor.sdk.controllers.BaseFragment; + +public class GroupInfoEditFragment extends BaseFragment { + + public GroupInfoEditFragment() { + setRootFragment(true); + } +} diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java index a43568d994..97c2a438c7 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java @@ -14,8 +14,6 @@ import android.view.View; import android.view.ViewGroup; import android.widget.AbsListView; -import android.widget.AdapterView; -import android.widget.CompoundButton; import android.widget.TextView; import android.widget.Toast; @@ -24,7 +22,6 @@ import com.google.i18n.phonenumbers.Phonenumber; import java.util.ArrayList; -import java.util.HashSet; import im.actor.core.entity.GroupMember; import im.actor.core.entity.GroupType; @@ -34,9 +31,6 @@ import im.actor.core.viewmodel.UserPhone; import im.actor.core.viewmodel.UserVM; import im.actor.runtime.actors.messages.Void; -import im.actor.runtime.mvvm.Value; -import im.actor.runtime.mvvm.ValueDoubleChangedListener; -import im.actor.runtime.mvvm.ValueListener; import im.actor.sdk.ActorSDK; import im.actor.sdk.ActorSDKLauncher; import im.actor.sdk.ActorStyle; @@ -50,8 +44,6 @@ import im.actor.sdk.view.TintImageView; import im.actor.sdk.view.adapters.RecyclerListView; import im.actor.sdk.view.avatar.AvatarView; -import im.actor.sdk.view.avatar.CoverAvatarView; -import im.actor.sdk.util.Fonts; import static im.actor.sdk.util.ActorSDKMessenger.groups; import static im.actor.sdk.util.ActorSDKMessenger.messenger; @@ -112,30 +104,28 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa // Views View header = inflater.inflate(R.layout.fragment_group_header, listView, false); + TextView title = (TextView) header.findViewById(R.id.title); + TextView subtitle = (TextView) header.findViewById(R.id.subtitle); avatarView = (AvatarView) header.findViewById(R.id.avatar); avatarView.init(Screen.dp(48), 22); TextView aboutTV = (TextView) header.findViewById(R.id.about); View aboutCont = header.findViewById(R.id.aboutContainer); - View addMemberCont = header.findViewById(R.id.addMemberCont); - View membersCont = header.findViewById(R.id.membersCont); - View leaveCont = header.findViewById(R.id.leaveChannelCont); + TextView addMember = (TextView) header.findViewById(R.id.addMemberAction); + TextView members = (TextView) header.findViewById(R.id.viewMembersAction); + TextView leaveAction = (TextView) header.findViewById(R.id.leaveAction); - TextView title = (TextView) header.findViewById(R.id.title); View descriptionContainer = header.findViewById(R.id.descriptionContainer); SwitchCompat isNotificationsEnabled = (SwitchCompat) header.findViewById(R.id.enableNotifications); - TextView memberCount = (TextView) header.findViewById(R.id.membersCount); - // TextView settingsHeaderText = (TextView) header.findViewById(R.id.settings_header_text); - // - // TextView membersHeaderText = (TextView) header.findViewById(R.id.membersTitle); // Styling // ((TextView) header.findViewById(R.id.about_hint)).setTextColor(style.getTextSecondaryColor()); header.setBackgroundColor(style.getMainBackgroundColor()); header.findViewById(R.id.avatarContainer).setBackgroundColor(style.getToolBarColor()); title.setTextColor(style.getProfileTitleColor()); + subtitle.setTextColor(style.getProfileSubtitleColor()); + aboutTV.setTextColor(style.getTextPrimaryColor()); - memberCount.setTextColor(style.getProfileSubtitleColor()); // settingsHeaderText.setTextColor(style.getSettingsCategoryTextColor()); ((TintImageView) header.findViewById(R.id.settings_notification_icon)) @@ -144,55 +134,37 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa .setTint(style.getSettingsIconColor()); ((TextView) header.findViewById(R.id.settings_notifications_title)) .setTextColor(style.getTextPrimaryColor()); - ((TextView) header.findViewById(R.id.add_member_title)) - .setTextColor(style.getTextPrimaryColor()); - ((TextView) header.findViewById(R.id.members_tite)) + ((TextView) header.findViewById(R.id.addMemberAction)) .setTextColor(style.getTextPrimaryColor()); - ((TextView) header.findViewById(R.id.leave_channel_title)) - .setTextColor(style.getTextDangerColor()); + members.setTextColor(style.getTextPrimaryColor()); + leaveAction.setTextColor(style.getTextDangerColor()); if (groupVM.getGroupType() == GroupType.CHANNEL) { - ((TextView) header.findViewById(R.id.leave_channel_title)) - .setText(R.string.group_leave_channel); + leaveAction.setText(R.string.group_leave_channel); } else { - ((TextView) header.findViewById(R.id.leave_channel_title)) - .setText(R.string.group_leave); + leaveAction.setText(R.string.group_leave); } - // header.findViewById(R.id.after_about_divider).setBackgroundColor(style.getBackyardBackgroundColor()); header.findViewById(R.id.after_settings_divider).setBackgroundColor(style.getBackyardBackgroundColor()); - // Avatar - // bind(avatarView, groupVM.getAvatar()); + // + // Header + // avatarView.bind(groupVM.getAvatar().get(), groupVM.getName().get(), groupVM.getId()); avatarView.setOnClickListener(view -> { startActivity(ViewAvatarActivity.viewGroupAvatar(chatId, getActivity())); }); - - // Title - bind(title, groupVM.getName()); - - // Owned by + bind(groupVM.getName(), name -> { + title.setText(name); + }); bind(groupVM.getMembersCount(), val -> { - if (val != null) { - memberCount.setText(messenger().getFormatter().formatGroupMembers(val)); - } else { - memberCount.setText(""); - } + subtitle.setText(messenger().getFormatter().formatGroupMembers(val)); }); // About + bind(groupVM.getOwnerId(), groupVM.getAbout(), (ownerId, valueModel, about, valueModel2) -> { - bind(groupVM.getOwnerId(), groupVM.getAbout(), (ownerId, valueModel, theme, valueModel2) -> { - boolean isVisible; - if (theme != null) { - isVisible = true; - aboutTV.setText(theme); - } else { - aboutTV.setText(R.string.about_group_empty); - isVisible = ownerId == myUid(); - } - descriptionContainer.setVisibility(isVisible ? View.VISIBLE : View.GONE); + descriptionContainer.setVisibility(about != null ? View.VISIBLE : View.GONE); if (ownerId == myUid()) { aboutCont.setOnClickListener(view -> { @@ -204,7 +176,6 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa }); // Notifications - isNotificationsEnabled.setChecked(messenger().isNotificationsEnabled(Peer.group(chatId))); isNotificationsEnabled.setOnCheckedChangeListener((buttonView, isChecked) -> { messenger().changeNotificationsEnabled(Peer.group(chatId), isChecked); @@ -214,98 +185,66 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa }); // Add Member - - addMemberCont.setOnClickListener(view -> { - startActivity(new Intent(getActivity(), AddMemberActivity.class) - .putExtra("GROUP_ID", chatId)); - }); - bind(groupVM.getIsCanInviteMembers(), (canInvite) -> { if (canInvite) { - addMemberCont.setVisibility(View.VISIBLE); + addMember.setVisibility(View.VISIBLE); } else { - addMemberCont.setVisibility(View.GONE); + addMember.setVisibility(View.GONE); } }); + addMember.setOnClickListener(view -> { + startActivity(new Intent(getActivity(), AddMemberActivity.class) + .putExtra("GROUP_ID", chatId)); + }); // Leave - - leaveCont.setOnClickListener(view1 -> { - leaveGroup(); + leaveAction.setOnClickListener(view1 -> { + new AlertDialog.Builder(getActivity()) + .setMessage(getString(R.string.alert_leave_group_message).replace("%1$s", + groupVM.getName().get())) + .setPositiveButton(R.string.alert_leave_group_yes, (dialog2, which) -> { + execute(messenger().leaveGroup(chatId)); + }) + .setNegativeButton(R.string.dialog_cancel, null) + .show() + .setCanceledOnTouchOutside(true); }); - // Hide Leave button if we can view members - // In this case menu will have such button - bind(groupVM.getIsCanViewMembers(), (canView) -> { - if (canView) { - leaveCont.setVisibility(View.GONE); - } else { - leaveCont.setVisibility(View.VISIBLE); - } - // Invalidate options menu for menu recreation - getActivity().invalidateOptionsMenu(); - }); - // Members - + // Showing member only when members available and async members is enabled bind(groupVM.getIsCanViewMembers(), groupVM.getIsAsyncMembers(), (canViewMembers, vm1, isAsync, vm2) -> { if (canViewMembers) { if (isAsync) { - membersCont.setVisibility(View.VISIBLE); + members.setVisibility(View.VISIBLE); header.findViewById(R.id.after_settings_divider).setVisibility(View.GONE); } else { - membersCont.setVisibility(View.GONE); + members.setVisibility(View.GONE); header.findViewById(R.id.after_settings_divider).setVisibility(View.VISIBLE); } } else { header.findViewById(R.id.after_settings_divider).setVisibility(View.GONE); } }); - membersCont.setOnClickListener(view -> { + members.setOnClickListener(view -> { startActivity(new Intent(getContext(), MembersActivity.class) .putExtra(Intents.EXTRA_GROUP_ID, groupVM.getId())); }); listView.addHeaderView(header, null, false); - // - // Footer - // - -// // View -// View footer = inflater.inflate(R.layout.fragment_group_add, listView, false); -// TextView name = (TextView) footer.findViewById(R.id.name); -// TintImageView addIcon = (TintImageView) footer.findViewById(R.id.add_icon); -// -// // Style -// footer.findViewById(R.id.bottom_divider).setBackgroundColor(style.getBackyardBackgroundColor()); -// name.setTextColor(style.getActionAddContactColor()); -// name.setTypeface(Fonts.medium()); -// addIcon.setTint(style.getGroupActionAddIconColor()); -// addIcon.setTint(style.getActionAddContactColor()); -// -// footer.findViewById(R.id.addUser).setOnClickListener(v -> { -// startActivity(new Intent(getActivity(), AddMemberActivity.class) -// .putExtra("GROUP_ID", chatId)); -// }); -// -// listView.addFooterView(footer, null, false); - - // // Members // groupUserAdapter = new MembersAdapter(getActivity()); - bind(groupVM.getIsAsyncMembers(), groupVM.getMembers(), (isAsyncMembers, valueModel, members, valueModel2) -> { + bind(groupVM.getIsAsyncMembers(), groupVM.getMembers(), (isAsyncMembers, valueModel, memberList, valueModel2) -> { if (isAsyncMembers) { groupUserAdapter.setMembers(new ArrayList<>()); } else { - groupUserAdapter.setMembers(members); + groupUserAdapter.setMembers(memberList); } }); listView.setAdapter(groupUserAdapter); - listView.setOnItemClickListener((parent, view, position, id) -> { Object item = parent.getItemAtPosition(position); if (item != null && item instanceof GroupMember) { @@ -333,6 +272,10 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa return false; }); + // + // Scroll handling + // + listView.setOnScrollListener(new AbsListView.OnScrollListener() { @Override public void onScrollStateChanged(AbsListView view, int scrollState) { @@ -358,7 +301,6 @@ public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCoun // Placeholder // - bind(groupVM.isMember(), (isMember) -> { notMemberView.setVisibility(isMember ? View.GONE : View.VISIBLE); getActivity().invalidateOptionsMenu(); @@ -368,9 +310,6 @@ public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCoun } public void onMemberClicked(UserVM userVM) { - - boolean isOwned = groupVM.getOwnerId().get() == myUid(); - new AlertDialog.Builder(getActivity()) .setItems(new CharSequence[]{ getString(R.string.group_context_message).replace("{0}", userVM.getName().get()), @@ -432,9 +371,27 @@ public void onError(Exception e) { .setCanceledOnTouchOutside(true); } - public void updateBar(int offset) { + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater menuInflater) { + super.onCreateOptionsMenu(menu, menuInflater); + if (groupVM.isMember().get()) { + if (groupVM.getIsCanEditInfo().get()) { + menuInflater.inflate(R.menu.group_info, menu); + } + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == R.id.editTitle) { + startActivity(Intents.editGroupTitle(chatId, getActivity())); + } else if (item.getItemId() == R.id.changePhoto) { + startActivity(ViewAvatarActivity.viewGroupAvatar(chatId, getActivity())); + } + return super.onOptionsItemSelected(item); + } - // avatarView.setOffset(offset); + public void updateBar(int offset) { int baseColor = getResources().getColor(R.color.primary); ActorStyle style = ActorSDK.sharedActor().style; @@ -458,48 +415,6 @@ public void updateBar(int offset) { } } - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater menuInflater) { - super.onCreateOptionsMenu(menu, menuInflater); - if (groupVM.isMember().get()) { - menuInflater.inflate(R.menu.group_info, menu); - if (!groupVM.getIsCanViewMembers().get()) { - menu.findItem(R.id.leaveGroup).setVisible(false); - } else { - if (groupVM.getGroupType() == GroupType.CHANNEL) { - menu.findItem(R.id.leaveGroup).setTitle(R.string.group_leave_channel); - } else { - menu.findItem(R.id.leaveGroup).setTitle(R.string.group_leave); - } - } - } - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (item.getItemId() == R.id.leaveGroup) { - leaveGroup(); - return true; - } else if (item.getItemId() == R.id.editTitle) { - startActivity(Intents.editGroupTitle(chatId, getActivity())); - } else if (item.getItemId() == R.id.changePhoto) { - startActivity(ViewAvatarActivity.viewGroupAvatar(chatId, getActivity())); - } - return super.onOptionsItemSelected(item); - } - - private void leaveGroup() { - new AlertDialog.Builder(getActivity()) - .setMessage(getString(R.string.alert_leave_group_message).replace("%1$s", - groupVM.getName().get())) - .setPositiveButton(R.string.alert_leave_group_yes, (dialog2, which) -> { - execute(messenger().leaveGroup(chatId)); - }) - .setNegativeButton(R.string.dialog_cancel, null) - .show() - .setCanceledOnTouchOutside(true); - } - @Override public void onDestroyView() { super.onDestroyView(); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/InviteLinkActivity.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/InviteLinkActivity.java index eadba566b3..42ae2d958f 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/InviteLinkActivity.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/InviteLinkActivity.java @@ -17,7 +17,7 @@ protected void onCreate(Bundle savedInstanceState) { int chatId = getIntent().getIntExtra(Intents.EXTRA_GROUP_ID, 0); if (savedInstanceState == null) { - showFragment(InviteLinkFragment.create(chatId), false, false); + showFragment(InviteLinkFragment.create(chatId), false); } } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/MembersActivity.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/MembersActivity.java index b5d1278c15..8f834462a1 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/MembersActivity.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/MembersActivity.java @@ -12,7 +12,7 @@ protected void onCreate(Bundle savedInstanceState) { if (savedInstanceState == null) { int groupId = getIntent().getIntExtra(Intents.EXTRA_GROUP_ID, 0); - showFragment(MembersFragment.create(groupId), false, false); + showFragment(MembersFragment.create(groupId), false); } } } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/profile/ProfileActivity.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/profile/ProfileActivity.java index dda21135a7..25527bf018 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/profile/ProfileActivity.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/profile/ProfileActivity.java @@ -44,7 +44,7 @@ protected void onCreate(Bundle savedInstanceState) { if (fragment == null) { fragment = ProfileFragment.create(uid); } - showFragment(fragment, false, false); + showFragment(fragment, false); } } } \ No newline at end of file diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/BlockedListActivity.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/BlockedListActivity.java index ad10540d9c..dc745ba363 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/BlockedListActivity.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/BlockedListActivity.java @@ -14,7 +14,7 @@ protected void onCreate(Bundle savedInstanceState) { if (savedInstanceState == null) { - showFragment(new BlockedListFragment(), false, false); + showFragment(new BlockedListFragment(), false); } } } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/ChatSettingsActivity.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/ChatSettingsActivity.java index eefc14e4d4..d28b5507a6 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/ChatSettingsActivity.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/ChatSettingsActivity.java @@ -24,7 +24,7 @@ protected void onCreate(Bundle savedInstanceState) { } if (savedInstanceState == null) { - showFragment(fragment, false, false); + showFragment(fragment, false); } } } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/EditAboutActivity.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/EditAboutActivity.java index 66a3644091..d239b649af 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/EditAboutActivity.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/EditAboutActivity.java @@ -25,7 +25,7 @@ protected void onCreate(Bundle savedInstanceState) { } if (savedInstanceState == null) { - showFragment(EditAboutFragment.editAbout(type, id), false, false); + showFragment(EditAboutFragment.editAbout(type, id), false); } } } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/EditNameActivity.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/EditNameActivity.java index 790715da2d..b6c5b46fe9 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/EditNameActivity.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/EditNameActivity.java @@ -34,7 +34,7 @@ protected void onCreate(Bundle savedInstanceState) { } if (savedInstanceState == null) { - showFragment(EditNameFragment.editName(type, id), false, false); + showFragment(EditNameFragment.editName(type, id), false); } } } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/MyProfileActivity.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/MyProfileActivity.java index ab18972c6e..a135b99c26 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/MyProfileActivity.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/MyProfileActivity.java @@ -30,7 +30,7 @@ protected void onCreate(Bundle savedInstanceState) { fragment = new ActorSettingsFragment(); } - showFragment(fragment, false, false); + showFragment(fragment, false); } } } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/NotificationsActivity.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/NotificationsActivity.java index dbcf1355ce..ca1354e1b1 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/NotificationsActivity.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/NotificationsActivity.java @@ -14,7 +14,7 @@ protected void onCreate(Bundle savedInstanceState) { getSupportActionBar().setTitle(R.string.not_title); if (savedInstanceState == null) { - showFragment(new NotificationsFragment(), false, false); + showFragment(new NotificationsFragment(), false); } } } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/PickWallpaperActivity.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/PickWallpaperActivity.java index c6f02a071d..d3e3008348 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/PickWallpaperActivity.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/PickWallpaperActivity.java @@ -16,7 +16,7 @@ protected void onCreate(Bundle savedInstanceState) { getSupportActionBar().setTitle(R.string.wallpaper); if (savedInstanceState == null) { - showFragment(PickWallpaperFragment.chooseWallpaper(id), false, false); + showFragment(PickWallpaperFragment.chooseWallpaper(id), false); } } } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/SecuritySettingsActivity.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/SecuritySettingsActivity.java index 8222253a06..5984eae488 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/SecuritySettingsActivity.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/SecuritySettingsActivity.java @@ -23,7 +23,7 @@ protected void onCreate(Bundle savedInstanceState) { } if (savedInstanceState == null) { - showFragment(fragment, false, false); + showFragment(fragment, false); } } } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_group_header.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_group_header.xml index c8890a432f..42b25853d7 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_group_header.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_group_header.xml @@ -45,14 +45,13 @@ android:textSize="20sp" /> - - - - - - - - - - - - - - - + android:gravity="center_vertical" + android:paddingLeft="72dp" + android:paddingRight="8dp" + android:text="@string/group_members_header" + android:textSize="16sp" /> - - - - - + android:gravity="center_vertical" + android:paddingLeft="72dp" + android:paddingRight="8dp" + android:text="@string/group_add_member" + android:textSize="16sp" /> - - - - - + android:gravity="center_vertical" + android:paddingLeft="72dp" + android:paddingRight="8dp" + android:text="@string/group_leave_channel" + android:textSize="16sp" /> - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/menu/group_info.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/menu/group_info.xml index b84f7594bc..017d350097 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/menu/group_info.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/menu/group_info.xml @@ -14,7 +14,4 @@ android:id="@+id/changePhoto" android:title="@string/group_menu_change_photo" /> - \ No newline at end of file diff --git a/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/mvvm/ValueDoubleListener.java b/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/mvvm/ValueDoubleListener.java new file mode 100644 index 0000000000..a706c10b1c --- /dev/null +++ b/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/mvvm/ValueDoubleListener.java @@ -0,0 +1,9 @@ +package im.actor.runtime.mvvm; + +import com.google.j2objc.annotations.ObjectiveCName; + +public interface ValueDoubleListener { + + @ObjectiveCName("onChanged:") + void onChanged(T1 val1, T2 val2); +} diff --git a/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/mvvm/ValueListener.java b/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/mvvm/ValueListener.java index 8504df22e9..4a9fc5f5da 100644 --- a/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/mvvm/ValueListener.java +++ b/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/mvvm/ValueListener.java @@ -7,6 +7,7 @@ import com.google.j2objc.annotations.ObjectiveCName; public interface ValueListener { + @ObjectiveCName("onChanged:") void onChanged(T val); } \ No newline at end of file From ab3fbf2d14777f90861a68c482d35da619ae4da0 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Sun, 17 Jul 2016 20:28:12 +0300 Subject: [PATCH 047/414] feat(android): New Group Edit Info activity --- .../sdk-core-android/android-sdk/build.gradle | 1 + .../android-sdk/src/main/AndroidManifest.xml | 6 + .../actor/sdk/controllers/BaseFragment.java | 78 +++++-- .../fragment/preview/ViewAvatarActivity.java | 4 +- ...itActivity.java => GroupEditActivity.java} | 8 +- .../controllers/group/GroupEditFragment.java | 210 ++++++++++++++++++ .../group/GroupInfoEditFragment.java | 10 - .../controllers/group/GroupInfoFragment.java | 31 ++- .../settings/EditAboutFragment.java | 12 - .../settings/EditNameFragment.java | 12 - .../tools/MediaPickerCallback.java | 3 + .../tools/MediaPickerFragment.java | 59 ++++- .../im/actor/sdk/view/avatar/AvatarView.java | 3 + .../main/res/layout/fragment_edit_info.xml | 90 ++++++++ .../android-sdk/src/main/res/menu/avatar.xml | 2 +- .../src/main/res/menu/group_info.xml | 9 +- .../src/main/res/values-ar/ui_text.xml | 2 +- .../src/main/res/values-fa/ui_text.xml | 2 +- .../src/main/res/values-pt-rBR/ui_text.xml | 2 +- .../src/main/res/values-pt-rPT/ui_text.xml | 2 +- .../src/main/res/values-ru/ui_text.xml | 2 +- .../src/main/res/values-zh/ui_text.xml | 2 +- .../src/main/res/values/ui_text.xml | 6 +- .../main/java/im/actor/core/Messenger.java | 20 +- 24 files changed, 469 insertions(+), 107 deletions(-) rename actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/{GroupInfoEditActivity.java => GroupEditActivity.java} (56%) create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupEditFragment.java delete mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoEditFragment.java create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_edit_info.xml diff --git a/actor-sdk/sdk-core-android/android-sdk/build.gradle b/actor-sdk/sdk-core-android/android-sdk/build.gradle index 568d657b1c..f5943d3b36 100644 --- a/actor-sdk/sdk-core-android/android-sdk/build.gradle +++ b/actor-sdk/sdk-core-android/android-sdk/build.gradle @@ -75,6 +75,7 @@ dependencies { compile 'com.facebook.fresco:webpsupport:0.10.0' // Not Enabling animated WebP yet compile 'com.github.castorflex.smoothprogressbar:library-circular:1.2.0' compile 'com.facebook.rebound:rebound:0.3.8' + compile 'com.rengwuxian.materialedittext:library:2.1.4' compile 'com.soundcloud.android:android-crop:1.0.0@aar' compile('com.github.chrisbanes.photoview:library:1.2.4') { diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/AndroidManifest.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/AndroidManifest.xml index dffc160e97..8582179c9a 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/AndroidManifest.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/AndroidManifest.xml @@ -145,6 +145,12 @@ android:theme="@style/ProfileActivityTheme" android:windowSoftInputMode="adjustNothing|stateAlwaysHidden" /> + + void execute(Promise promise) { - execute(promise, R.string.progress_common); + public Promise execute(Promise promise) { + return execute(promise, R.string.progress_common); } - public void execute(Promise promise, int title) { + public Promise execute(Promise promise, int title) { final ProgressDialog dialog = ProgressDialog.show(getContext(), "", getString(title), true, false); - promise - .then(new Consumer() { - @Override - public void apply(T t) { - dismissDialog(dialog); - } - }) - .failure(new Consumer() { - @Override - public void apply(Exception e) { - dismissDialog(dialog); - } - }); + promise.then(new Consumer() { + @Override + public void apply(T t) { + dismissDialog(dialog); + } + }).failure(new Consumer() { + @Override + public void apply(Exception e) { + dismissDialog(dialog); + } + }); + return promise; } public View buildRecord(String titleText, String valueText, @@ -429,6 +432,47 @@ public void onPause() { pending.clear(); } + public void finishActivity() { + Activity a = getActivity(); + if (a != null) { + a.finish(); + } + } + + @Override + public void onUriPicked(Uri uri) { + + } + + @Override + public void onFilesPicked(List paths) { + + } + + @Override + public void onPhotoPicked(String path) { + + } + + @Override + public void onVideoPicked(String path) { + + } + + @Override + public void onPhotoCropped(String path) { + + } + + @Override + public void onContactPicked(String name, List phones, List emails, byte[] avatar) { + + } + + @Override + public void onLocationPicked(double latitude, double longitude, String street, String place) { + + } private class WrappedPromise extends Promise { diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/fragment/preview/ViewAvatarActivity.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/fragment/preview/ViewAvatarActivity.java index 94a0ecc851..2246b09200 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/fragment/preview/ViewAvatarActivity.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/fragment/preview/ViewAvatarActivity.java @@ -252,9 +252,7 @@ public void onDownloaded(FileSystemReference reference) { public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.avatar, menu); - if (peer.getPeerType() == PeerType.GROUP) { - menu.findItem(R.id.editAvatar).setVisible(true); - } else if (peer.getPeerType() == PeerType.PRIVATE && peer.getPeerId() == myUid()) { + if (peer.getPeerType() == PeerType.PRIVATE && peer.getPeerId() == myUid()) { menu.findItem(R.id.editAvatar).setVisible(true); } else { menu.findItem(R.id.editAvatar).setVisible(false); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoEditActivity.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupEditActivity.java similarity index 56% rename from actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoEditActivity.java rename to actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupEditActivity.java index c738f6c6c9..71b2892ff4 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoEditActivity.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupEditActivity.java @@ -2,16 +2,18 @@ import android.os.Bundle; +import im.actor.sdk.controllers.Intents; import im.actor.sdk.controllers.activity.BaseFragmentActivity; -public class GroupInfoEditActivity extends BaseFragmentActivity { - +public class GroupEditActivity extends BaseFragmentActivity { + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (savedInstanceState == null) { - showFragment(new GroupInfoEditFragment(), false); + showFragment(GroupEditFragment.create( + getIntent().getIntExtra(Intents.EXTRA_GROUP_ID, 0)), false); } } } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupEditFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupEditFragment.java new file mode 100644 index 0000000000..fb1665c998 --- /dev/null +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupEditFragment.java @@ -0,0 +1,210 @@ +package im.actor.sdk.controllers.group; + +import android.net.Uri; +import android.os.Bundle; +import android.support.v7.app.AlertDialog; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.EditText; +import android.widget.Toast; + +import com.rengwuxian.materialedittext.MaterialEditText; + +import org.jetbrains.annotations.Nullable; + +import im.actor.core.util.JavaUtil; +import im.actor.core.viewmodel.GroupVM; +import im.actor.runtime.actors.messages.Void; +import im.actor.runtime.promise.Promise; +import im.actor.sdk.R; +import im.actor.sdk.controllers.BaseFragment; +import im.actor.sdk.controllers.tools.MediaPickerCallback; +import im.actor.sdk.controllers.tools.MediaPickerFragment; +import im.actor.sdk.util.Screen; +import im.actor.sdk.view.avatar.AvatarView; + +import static im.actor.sdk.util.ActorSDKMessenger.messenger; + +public class GroupEditFragment extends BaseFragment implements MediaPickerCallback { + + public static GroupEditFragment create(int groupId) { + Bundle bundle = new Bundle(); + bundle.putInt("groupId", groupId); + GroupEditFragment editFragment = new GroupEditFragment(); + editFragment.setArguments(bundle); + return editFragment; + } + + private int groupId; + private GroupVM groupVM; + private AvatarView avatarView; + private MaterialEditText titleEditText; + private EditText descriptionEditText; + + public GroupEditFragment() { + setTitle(R.string.group_title); + setHomeAsUp(true); + setShowHome(true); + setRootFragment(true); + } + + @Override + public void onCreate(Bundle saveInstance) { + super.onCreate(saveInstance); + groupId = getArguments().getInt("groupId"); + groupVM = messenger().getGroup(groupId); + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View res = inflater.inflate(R.layout.fragment_edit_info, container, false); + res.findViewById(R.id.rootContainer).setBackgroundColor(style.getBackyardBackgroundColor()); + res.findViewById(R.id.topContainer).setBackgroundColor(style.getMainBackgroundColor()); + + avatarView = (AvatarView) res.findViewById(R.id.avatar); + avatarView.init(Screen.dp(52), 24); + avatarView.bind(groupVM.getAvatar().get(), groupVM.getName().get(), groupId); + avatarView.setOnClickListener(v -> { + onAvatarClicked(); + }); + + titleEditText = (MaterialEditText) res.findViewById(R.id.name); + titleEditText.setTextColor(style.getTextPrimaryColor()); + titleEditText.setBaseColor(style.getAccentColor()); + titleEditText.setMetHintTextColor(style.getTextHintColor()); + titleEditText.setText(groupVM.getName().get()); + titleEditText.addTextChangedListener(new TextWatcher() { + + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { + + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { + + } + + @Override + public void afterTextChanged(Editable editable) { + avatarView.updatePlaceholder(editable.toString(), groupId); + } + }); + + descriptionEditText = (EditText) res.findViewById(R.id.description); + descriptionEditText.setTextColor(style.getTextPrimaryColor()); + descriptionEditText.setHintTextColor(style.getTextHintColor()); + descriptionEditText.setText(groupVM.getAbout().get()); + + // Media Picker + getChildFragmentManager().beginTransaction() + .add(new MediaPickerFragment(), "picker") + .commitNow(); + + return res; + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + super.onCreateOptionsMenu(menu, inflater); + inflater.inflate(R.menu.next, menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == R.id.next) { + + Promise res = Promise.success(null); + boolean isPerformed = false; + + if (titleEditText != null) { + String title = titleEditText.getText().toString().trim(); + if (title.length() > 0) { + if (!title.equals(messenger().getGroup(groupId).getName().get())) { + res = res.chain(r -> messenger().editGroupTitle(groupId, title)); + isPerformed = true; + } + } else { + return true; + } + } + + if (descriptionEditText != null) { + String description = descriptionEditText.getText().toString().trim(); + if (description.length() == 0) { + description = null; + } + + if (!JavaUtil.equalsE(description, groupVM.getAbout().get())) { + final String finalDescription = description; + res = res.chain(r -> messenger().editGroupAbout(groupId, finalDescription)); + isPerformed = true; + } + } + + if (isPerformed) { + execute(res).then(r -> { + finishActivity(); + }).failure(r -> { + Toast.makeText(getActivity(), R.string.toast_unable_change, Toast.LENGTH_SHORT).show(); + }); + } else { + finishActivity(); + } + + return true; + } else { + return super.onOptionsItemSelected(item); + } + } + + public void onAvatarClicked() { + CharSequence[] args; + if (groupVM.getAvatar().get() != null) { + args = new CharSequence[]{getString(R.string.pick_photo_camera), + getString(R.string.pick_photo_gallery), + getString(R.string.pick_photo_remove)}; + } else { + args = new CharSequence[]{getString(R.string.pick_photo_camera), + getString(R.string.pick_photo_gallery)}; + } + new AlertDialog.Builder(getActivity()).setItems(args, (d, which) -> { + if (which == 0) { + findPicker().requestPhoto(true); + } else if (which == 1) { + findPicker().requestGallery(true); + } else if (which == 2) { + messenger().removeGroupAvatar(groupId); + avatarView.bind(null, groupVM.getName().get(), groupId); + } + }).show(); + } + + @Override + public void onPhotoCropped(String path) { + messenger().changeGroupAvatar(groupId, path); + avatarView.bindRaw(path); + } + + private MediaPickerFragment findPicker() { + return (MediaPickerFragment) getChildFragmentManager().findFragmentByTag("picker"); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + titleEditText = null; + descriptionEditText = null; + if (avatarView != null) { + avatarView.unbind(); + avatarView = null; + } + } +} diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoEditFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoEditFragment.java deleted file mode 100644 index fd1d2012b1..0000000000 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoEditFragment.java +++ /dev/null @@ -1,10 +0,0 @@ -package im.actor.sdk.controllers.group; - -import im.actor.sdk.controllers.BaseFragment; - -public class GroupInfoEditFragment extends BaseFragment { - - public GroupInfoEditFragment() { - setRootFragment(true); - } -} diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java index 97c2a438c7..0de7b452e1 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java @@ -152,7 +152,9 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa // avatarView.bind(groupVM.getAvatar().get(), groupVM.getName().get(), groupVM.getId()); avatarView.setOnClickListener(view -> { - startActivity(ViewAvatarActivity.viewGroupAvatar(chatId, getActivity())); + if (groupVM.getAvatar().get() != null) { + startActivity(ViewAvatarActivity.viewGroupAvatar(chatId, getActivity())); + } }); bind(groupVM.getName(), name -> { title.setText(name); @@ -163,16 +165,8 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa // About bind(groupVM.getOwnerId(), groupVM.getAbout(), (ownerId, valueModel, about, valueModel2) -> { - + aboutTV.setText(about); descriptionContainer.setVisibility(about != null ? View.VISIBLE : View.GONE); - - if (ownerId == myUid()) { - aboutCont.setOnClickListener(view -> { - startActivity(Intents.editGroupAbout(groupVM.getId(), getActivity())); - }); - } else { - aboutCont.setOnClickListener(null); - } }); // Notifications @@ -375,20 +369,21 @@ public void onError(Exception e) { public void onCreateOptionsMenu(Menu menu, MenuInflater menuInflater) { super.onCreateOptionsMenu(menu, menuInflater); if (groupVM.isMember().get()) { - if (groupVM.getIsCanEditInfo().get()) { - menuInflater.inflate(R.menu.group_info, menu); - } + MenuItem menuItem = menu.add(Menu.NONE, R.id.edit, Menu.NONE, R.string.actor_menu_edit); + menuItem.setIcon(R.drawable.ic_edit_white_24dp); + menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); } } @Override public boolean onOptionsItemSelected(MenuItem item) { - if (item.getItemId() == R.id.editTitle) { - startActivity(Intents.editGroupTitle(chatId, getActivity())); - } else if (item.getItemId() == R.id.changePhoto) { - startActivity(ViewAvatarActivity.viewGroupAvatar(chatId, getActivity())); + if (item.getItemId() == R.id.edit) { + startActivity(new Intent(getContext(), GroupEditActivity.class) + .putExtra(Intents.EXTRA_GROUP_ID, groupVM.getId())); + return true; + } else { + return super.onOptionsItemSelected(item); } - return super.onOptionsItemSelected(item); } public void updateBar(int offset) { diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/EditAboutFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/EditAboutFragment.java index de509f600f..65e49b71bb 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/EditAboutFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/EditAboutFragment.java @@ -93,18 +93,6 @@ public void onError(Exception e) { } }); //TODO: set group about - } else if (type == EditAboutActivity.TYPE_GROUP) { - execute(messenger().editGroupAbout(id, about), R.string.edit_about_process, new CommandCallback() { - @Override - public void onResult(Void res) { - getActivity().finish(); - } - - @Override - public void onError(Exception e) { - Toast.makeText(getActivity(), R.string.toast_unable_change, Toast.LENGTH_SHORT).show(); - } - }); } } }); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/EditNameFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/EditNameFragment.java index be9caad67a..6b31e34bbe 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/EditNameFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/EditNameFragment.java @@ -120,18 +120,6 @@ public void onResult(Boolean res) { getActivity().finish(); } - @Override - public void onError(Exception e) { - Toast.makeText(getActivity(), R.string.toast_unable_change, Toast.LENGTH_SHORT).show(); - } - }); - } else if (type == EditNameActivity.TYPE_GROUP) { - execute(messenger().editGroupTitle(id, name), R.string.edit_name_process, new CommandCallback() { - @Override - public void onResult(Void res) { - getActivity().finish(); - } - @Override public void onError(Exception e) { Toast.makeText(getActivity(), R.string.toast_unable_change, Toast.LENGTH_SHORT).show(); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/tools/MediaPickerCallback.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/tools/MediaPickerCallback.java index d656357bb2..10885ebe83 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/tools/MediaPickerCallback.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/tools/MediaPickerCallback.java @@ -5,6 +5,7 @@ import java.util.List; public interface MediaPickerCallback { + void onUriPicked(Uri uri); void onFilesPicked(List paths); @@ -13,6 +14,8 @@ public interface MediaPickerCallback { void onVideoPicked(String path); + void onPhotoCropped(String path); + void onContactPicked(String name, List phones, List emails, byte[] avatar); void onLocationPicked(double latitude, double longitude, String street, String place); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/tools/MediaPickerFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/tools/MediaPickerFragment.java index 9762ae9d86..741e983201 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/tools/MediaPickerFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/tools/MediaPickerFragment.java @@ -18,6 +18,8 @@ import android.support.v4.content.ContextCompat; import android.widget.Toast; +import com.soundcloud.android.crop.Crop; + import java.io.File; import java.util.ArrayList; import java.util.List; @@ -40,9 +42,14 @@ public class MediaPickerFragment extends BaseFragment { private static final int PERMISSIONS_REQUEST_CAMERA = 6; private String pendingFile; - + private boolean pickCropped; public void requestPhoto() { + requestPhoto(false); + } + + public void requestPhoto(boolean pickCropped) { + this.pickCropped = pickCropped; // // Checking permissions @@ -80,6 +87,7 @@ public void requestPhoto() { } public void requestVideo() { + this.pickCropped = false; // // Generating Temporary File Name @@ -99,22 +107,34 @@ public void requestVideo() { } public void requestGallery() { + requestGallery(false); + } + + public void requestGallery(boolean pickCropped) { + this.pickCropped = pickCropped; + Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI); intent.setType("image/* video/*"); startActivityForResult(intent, REQUEST_GALLERY); } public void requestFile() { + this.pickCropped = false; + Activity activity = getActivity(); startActivityForResult(new Intent(activity, FilePickerActivity.class), REQUEST_DOC); } public void requestLocation() { + this.pickCropped = false; + Intent intent = new Intent("im.actor.pickLocation_" + AndroidContext.getContext().getPackageName()); startActivityForResult(intent, REQUEST_LOCATION); } public void requestContact() { + this.pickCropped = false; + Intent intent = new Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI); startActivityForResult(intent, REQUEST_CONTACT); } @@ -125,17 +145,37 @@ public void onActivityResult(int requestCode, int resultCode, final Intent data) if (resultCode == Activity.RESULT_OK) { if (requestCode == REQUEST_GALLERY) { if (data.getData() != null) { - getCallback().onUriPicked(data.getData()); + if (pickCropped) { + pendingFile = generateRandomFile(".jpg"); + Crop.of(data.getData(), Uri.fromFile(new File(pendingFile))) + .asSquare() + .start(getContext(), this); + } else { + getCallback().onUriPicked(data.getData()); + } } } else if (requestCode == REQUEST_PHOTO) { if (pendingFile != null) { - getCallback().onPhotoPicked(pendingFile); + + String sourceFileName = pendingFile; Context context = getContext(); if (context != null) { MediaScannerConnection.scanFile(context, new String[]{pendingFile}, new String[]{"image/jpeg"}, null); } - pendingFile = null; + + if (pickCropped) { + pendingFile = generateRandomFile(".jpg"); + Crop.of(Uri.fromFile(new File(sourceFileName)), Uri.fromFile(new File(pendingFile))) + .asSquare() + .start(getContext(), this); + } else { + getCallback().onPhotoPicked(sourceFileName); + } + } + } else if (requestCode == Crop.REQUEST_CROP) { + if (pendingFile != null) { + getCallback().onPhotoCropped(pendingFile); } } else if (requestCode == REQUEST_VIDEO) { if (pendingFile != null) { @@ -227,8 +267,6 @@ public void onActivityResult(int requestCode, int resultCode, final Intent data) getCallback().onLocationPicked(data.getDoubleExtra("longitude", 0), data.getDoubleExtra("latitude", 0), data.getStringExtra("street"), data.getStringExtra("place")); } } - - } @Override @@ -246,6 +284,7 @@ public void onCreate(Bundle saveInstance) { super.onCreate(saveInstance); if (saveInstance != null) { pendingFile = saveInstance.getString("pendingFile", null); + pickCropped = saveInstance.getBoolean("pickCropped"); } } @@ -255,6 +294,7 @@ public void onSaveInstanceState(Bundle outState) { if (pendingFile != null) { outState.putString("pendingFile", pendingFile); } + outState.putBoolean("pickCropped", pickCropped); } private String generateRandomFile(String ext) { @@ -314,6 +354,13 @@ public void onVideoPicked(String path) { } } + @Override + public void onPhotoCropped(String path) { + if (callback != null) { + callback.onPhotoCropped(path); + } + } + @Override public void onContactPicked(String name, List phones, List emails, byte[] avatar) { if (callback != null) { diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/avatar/AvatarView.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/avatar/AvatarView.java index 48c19a4e1a..2f97d56a45 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/avatar/AvatarView.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/avatar/AvatarView.java @@ -99,6 +99,9 @@ public void bind(PublicGroup group) { bind(group.getAvatar(), group.getTitle(), group.getId()); } + public void updatePlaceholder(String title, int id) { + getHierarchy().setPlaceholderImage(new AvatarPlaceholderDrawable(title, id, placeholderTextSize, getContext())); + } public void bind(Avatar avatar, String title, int id) { // Same avatar diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_edit_info.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_edit_info.xml new file mode 100644 index 0000000000..634a604fa3 --- /dev/null +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_edit_info.xml @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/menu/avatar.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/menu/avatar.xml index 2dba5eb56b..dc5db8dde0 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/menu/avatar.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/menu/avatar.xml @@ -10,7 +10,7 @@ \ No newline at end of file diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/menu/group_info.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/menu/group_info.xml index 017d350097..e5f1d283ff 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/menu/group_info.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/menu/group_info.xml @@ -7,11 +7,8 @@ - - + android:id="@+id/edit" + android:icon="@drawable/ic_edit_white_36dp" + android:title="@string/actor_menu_edit" /> \ No newline at end of file diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-ar/ui_text.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-ar/ui_text.xml index bfbce0ad36..99ca76257d 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-ar/ui_text.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-ar/ui_text.xml @@ -269,7 +269,7 @@ صورة صورة المجموعة لا يوجد صورة - تعديل + تعديل ضع كـ… diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-fa/ui_text.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-fa/ui_text.xml index 87ebe8cdfb..643c8f71d8 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-fa/ui_text.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-fa/ui_text.xml @@ -345,7 +345,7 @@ نگاره نگاره گروه بدون نگاره - ویرایش + ویرایش تنظیم به عنوان... diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-pt-rBR/ui_text.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-pt-rBR/ui_text.xml index 9e788e621f..6146214650 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-pt-rBR/ui_text.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-pt-rBR/ui_text.xml @@ -268,7 +268,7 @@ Foto Foto do grupo Sem foto - Editar + Editar Definir como… diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-pt-rPT/ui_text.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-pt-rPT/ui_text.xml index 9b3e91e6fb..bff00d1cec 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-pt-rPT/ui_text.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-pt-rPT/ui_text.xml @@ -297,7 +297,7 @@ Foto Foto do grupo Ainda não tem foto - Editar + Editar Definir como diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-ru/ui_text.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-ru/ui_text.xml index 34df360584..628585ad26 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-ru/ui_text.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-ru/ui_text.xml @@ -356,7 +356,7 @@ Фото Фото группы Нет фото - Изменить + Изменить Установить как… diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-zh/ui_text.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-zh/ui_text.xml index 914703254a..e893710974 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-zh/ui_text.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-zh/ui_text.xml @@ -269,7 +269,7 @@ 头像 群组头像 未设置头像 - 编辑 + 编辑 设置为… diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml index f06802207d..49ec6e0e13 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml @@ -348,6 +348,7 @@ View {0} Remove {0} from group + Edit Change theme Change photo Integration token @@ -373,7 +374,7 @@ Photo Group photo No photo - Edit + Edit Set as… @@ -397,6 +398,9 @@ Name the channel Enter channel name and set optional channel picture + Name + Description + Add member diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java index 341d922809..fcf1682d25 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java @@ -1387,14 +1387,12 @@ public Command editName(final int uid, final String name) { * * @param gid group's id * @param title new group title - * @return Command for execution + * @return Promise for void */ @NotNull - @ObjectiveCName("editGroupTitleCommandWithGid:withTitle:") - public Command editGroupTitle(final int gid, final String title) { - return callback -> modules.getGroupsModule().editTitle(gid, title) - .then(v -> callback.onResult(v)) - .failure(e -> callback.onError(e)); + @ObjectiveCName("editGroupTitleWithGid:withTitle:") + public Promise editGroupTitle(final int gid, final String title) { + return modules.getGroupsModule().editTitle(gid, title); } /** @@ -1417,14 +1415,12 @@ public Command editGroupTheme(final int gid, final String theme) { * * @param gid group's id * @param about new group about - * @return Command for execution + * @return Promise for void */ @NotNull - @ObjectiveCName("editGroupAboutCommandWithGid:withAbout:") - public Command editGroupAbout(final int gid, final String about) { - return callback -> modules.getGroupsModule().editAbout(gid, about) - .then(v -> callback.onResult(v)) - .failure(e -> callback.onError(e)); + @ObjectiveCName("editGroupAboutWithGid:withAbout:") + public Promise editGroupAbout(final int gid, final String about) { + return modules.getGroupsModule().editAbout(gid, about); } /** From dd61a1f5b20963ad6598d3bc8f37bf29c4a87cd9 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Sun, 17 Jul 2016 21:02:12 +0300 Subject: [PATCH 048/414] feat(android): remove overflow menu --- .../toolbar/ChatToolbarFragment.java | 77 +++++++++---------- .../src/main/res/menu/chat_menu.xml | 24 +++--- 2 files changed, 47 insertions(+), 54 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/toolbar/ChatToolbarFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/toolbar/ChatToolbarFragment.java index d63856979f..83398ef06b 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/toolbar/ChatToolbarFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/toolbar/ChatToolbarFragment.java @@ -4,6 +4,7 @@ import android.app.Activity; import android.app.AlertDialog; import android.content.pm.PackageManager; +import android.graphics.Color; import android.graphics.PorterDuff; import android.os.Bundle; import android.support.v4.app.ActivityCompat; @@ -145,24 +146,18 @@ public void onConfigureActionBar(ActionBar actionBar) { public void onResume() { super.onResume(); - // Performing all required Data Binding here if (peer.getPeerType() == PeerType.PRIVATE) { // Loading user - final UserVM user = users().get(peer.getPeerId()); - if (user == null) { - - return; - } + UserVM user = users().get(peer.getPeerId()); // Binding User Avatar to Toolbar bind(barAvatar, user.getId(), user.getAvatar(), user.getName()); // Binding User name to Toolbar bind(barTitle, user.getName()); - bind(user.getIsVerified(), (val, valueModel) -> { barTitle.setCompoundDrawablesWithIntrinsicBounds(null, null, val ? new TintDrawable( @@ -186,16 +181,19 @@ public void onResume() { // Loading group GroupVM group = groups().get(peer.getPeerId()); - if (group == null) { - // finish(); - return; - } // Binding Group avatar to Toolbar bind(barAvatar, group.getId(), group.getAvatar(), group.getName()); // Binding Group title to Toolbar bind(barTitle, group.getName()); + if (group.getGroupType() == GroupType.CHANNEL) { + barTitle.setCompoundDrawablesWithIntrinsicBounds(new TintDrawable( + getResources().getDrawable(R.drawable.ic_megaphone_18dp_black), + Color.WHITE), null, null, null); + } else { + barTitle.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null); + } // Subtitle is always visible for Groups barSubtitleContainer.setVisibility(View.VISIBLE); @@ -208,12 +206,7 @@ public void onResume() { bindGroupTyping(barTyping, barTypingContainer, barSubtitle, messenger().getGroupTyping(group.getId())); } } - - // Show/Hide Avatar - if (!style.isShowAvatarInTitle() || (peer.getPeerType() == PeerType.PRIVATE && !style.isShowAvatarPrivateInTitle())) { - barAvatar.setVisibility(View.GONE); - } - + // Global Counter bind(messenger().getGlobalState().getGlobalCounter(), (val, valueModel) -> { if (val != null && val > 0) { @@ -233,31 +226,31 @@ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { inflater.inflate(R.menu.chat_menu, menu); // Show menu for opening chat contact - if (peer.getPeerType() == PeerType.PRIVATE) { - menu.findItem(R.id.contact).setVisible(true); - } else { - menu.findItem(R.id.contact).setVisible(false); - } +// if (peer.getPeerType() == PeerType.PRIVATE) { +// menu.findItem(R.id.contact).setVisible(true); +// } else { +// menu.findItem(R.id.contact).setVisible(false); +// } // Show menus for leave group and group info view - if (peer.getPeerType() == PeerType.GROUP) { - GroupVM groupVM = groups().get(peer.getPeerId()); - if (groupVM.isMember().get()) { - menu.findItem(R.id.leaveGroup).setVisible(true); - menu.findItem(R.id.groupInfo).setVisible(true); - } else { - menu.findItem(R.id.leaveGroup).setVisible(false); - menu.findItem(R.id.groupInfo).setVisible(false); - } - if (groupVM.getGroupType() == GroupType.GROUP) { - menu.findItem(R.id.clear).setVisible(true); - } else { - menu.findItem(R.id.clear).setVisible(false); - } - } else { - menu.findItem(R.id.groupInfo).setVisible(false); - menu.findItem(R.id.leaveGroup).setVisible(false); - } +// if (peer.getPeerType() == PeerType.GROUP) { +// GroupVM groupVM = groups().get(peer.getPeerId()); +// if (groupVM.isMember().get()) { +// menu.findItem(R.id.leaveGroup).setVisible(true); +// menu.findItem(R.id.groupInfo).setVisible(true); +// } else { +// menu.findItem(R.id.leaveGroup).setVisible(false); +// menu.findItem(R.id.groupInfo).setVisible(false); +// } +// if (groupVM.getGroupType() == GroupType.GROUP) { +// menu.findItem(R.id.clear).setVisible(true); +// } else { +// menu.findItem(R.id.clear).setVisible(false); +// } +// } else { +// menu.findItem(R.id.groupInfo).setVisible(false); +// menu.findItem(R.id.leaveGroup).setVisible(false); +// } // Voice and Video calls boolean callsEnabled = ActorSDK.sharedActor().isCallsEnabled(); @@ -291,7 +284,7 @@ public boolean onOptionsItemSelected(MenuItem item) { int i = item.getItemId(); if (i == android.R.id.home) { getActivity().finish(); - } else if (i == R.id.clear) { + } /*else if (i == R.id.clear) { new AlertDialog.Builder(getActivity()) .setMessage(R.string.alert_delete_all_messages_text) .setPositiveButton(R.string.alert_delete_all_messages_yes, (dialog, which) -> { @@ -338,7 +331,7 @@ public void onError(final Exception e) { ActorSDKLauncher.startProfileActivity(getActivity(), peer.getPeerId()); } else if (i == R.id.groupInfo) { ActorSDK.sharedActor().startGroupInfoActivity(getActivity(), peer.getPeerId()); - } else if (i == R.id.add_to_contacts) { + }*/ else if (i == R.id.add_to_contacts) { execute(messenger().addContact(peer.getPeerId())); } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/menu/chat_menu.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/menu/chat_menu.xml index 500ad7ad93..5a8e39faa5 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/menu/chat_menu.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/menu/chat_menu.xml @@ -26,16 +26,16 @@ android:title="" app:showAsAction="always" /> - - - - + + + + + + + + + + + + \ No newline at end of file From 486f7e0bd358ae06f95232a9b7f1facd290ad42e Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Sun, 17 Jul 2016 21:21:33 +0300 Subject: [PATCH 049/414] fix(android): Fix white avatars in ToolBar --- .../src/main/java/im/actor/sdk/view/ActorToolbar.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/ActorToolbar.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/ActorToolbar.java index 410176e154..b7b0994d7d 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/ActorToolbar.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/ActorToolbar.java @@ -18,6 +18,8 @@ import android.widget.ImageView; import android.widget.TextView; +import im.actor.sdk.view.avatar.AvatarView; + public class ActorToolbar extends Toolbar { public ActorToolbar(Context context, AttributeSet attrs, int defStyleAttr) { @@ -80,7 +82,7 @@ public static void doColorizing(View v, final ColorFilter colorFilter, int toolb ((ImageButton) v).getDrawable().setColorFilter(colorFilter); } - if (v instanceof ImageView) { + if (v instanceof ImageView && !(v instanceof AvatarView)) { ((ImageView) v).getDrawable().setAlpha(255); ((ImageView) v).getDrawable().setColorFilter(colorFilter); } From a7c7bf70c307322e55a9608dad537f1d36b7feec Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Sun, 17 Jul 2016 21:35:12 +0300 Subject: [PATCH 050/414] fix(android): Fix capitalization in group editing --- .../android-sdk/src/main/res/layout/fragment_edit_info.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_edit_info.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_edit_info.xml index 634a604fa3..9610ca22c4 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_edit_info.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_edit_info.xml @@ -45,6 +45,7 @@ android:layout_weight="1" android:hint="@string/group_name_hint" android:imeOptions="actionNext" + android:inputType="textCapWords" android:lines="1" android:singleLine="true" app:met_maxCharacters="50" @@ -64,6 +65,7 @@ android:background="@android:color/transparent" android:gravity="center_vertical" android:hint="@string/group_description_hint" + android:inputType="textCapSentences" android:minHeight="48dp" android:paddingBottom="8dp" android:paddingTop="8dp" /> From 3113bf506e0fb99a93422dc639c84fd6185261c0 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Mon, 18 Jul 2016 00:15:53 +0300 Subject: [PATCH 051/414] feat(android): customizable placeholders --- .../src/main/java/im/actor/sdk/ActorStyle.java | 17 +++++++++++++++++ .../view/avatar/AvatarPlaceholderDrawable.java | 16 +++------------- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorStyle.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorStyle.java index a7680696eb..95a729f7ff 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorStyle.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorStyle.java @@ -26,6 +26,23 @@ public void setDefaultBackgrouds(int[] defaultBackgrouds) { } } + private int[] defaultAvatarPlaceholders = new int[]{ + 0xff509dbb, + 0xff52b3cd, + 0xff7e6eeb, + 0xffde5447, + 0xffee7e37, + 0xffed608b, + 0xff6ac53c}; + + public int[] getDefaultAvatarPlaceholders() { + return defaultAvatarPlaceholders; + } + + public void setDefaultAvatarPlaceholders(int[] defaultAvatarPlaceholders) { + this.defaultAvatarPlaceholders = defaultAvatarPlaceholders; + } + ////////////////////////// // COLORS // ////////////////////////// diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/avatar/AvatarPlaceholderDrawable.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/avatar/AvatarPlaceholderDrawable.java index 8c59427a92..225e37bc48 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/avatar/AvatarPlaceholderDrawable.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/avatar/AvatarPlaceholderDrawable.java @@ -10,6 +10,7 @@ import android.text.TextPaint; import android.util.TypedValue; +import im.actor.sdk.ActorSDK; import im.actor.sdk.R; import im.actor.sdk.util.Fonts; @@ -19,7 +20,6 @@ public class AvatarPlaceholderDrawable extends Drawable { private static float TEXT_SIZE; private float selfTextSize; private static Paint CIRCLE_PAINT; - private static int[] COLORS; private Context ctx; private String title; @@ -47,17 +47,7 @@ public AvatarPlaceholderDrawable(String title, int id, float selfTextSize, Conte } } - if (COLORS == null) { - COLORS = new int[]{ - context.getResources().getColor(R.color.placeholder_0), - context.getResources().getColor(R.color.placeholder_1), - context.getResources().getColor(R.color.placeholder_2), - context.getResources().getColor(R.color.placeholder_3), - context.getResources().getColor(R.color.placeholder_4), - context.getResources().getColor(R.color.placeholder_5), - context.getResources().getColor(R.color.placeholder_6), - }; - } + int[] colors = ActorSDK.sharedActor().style.getDefaultAvatarPlaceholders(); if (CIRCLE_PAINT == null) { CIRCLE_PAINT = new Paint(Paint.ANTI_ALIAS_FLAG); @@ -74,7 +64,7 @@ public AvatarPlaceholderDrawable(String title, int id, float selfTextSize, Conte if (id == 0) { this.color = context.getResources().getColor(R.color.placeholder_empty); } else { - this.color = COLORS[Math.abs(id) % COLORS.length]; + this.color = colors[Math.abs(id) % colors.length]; } this.title = title; From 1a40f72f1e381d19c74a58d14451b1faf3bedc4d Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Mon, 18 Jul 2016 00:15:53 +0300 Subject: [PATCH 052/414] feat(android): customizable placeholders --- .../src/main/java/im/actor/sdk/ActorStyle.java | 17 +++++++++++++++++ .../view/avatar/AvatarPlaceholderDrawable.java | 16 +++------------- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorStyle.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorStyle.java index 6dd2be1b46..42bc992532 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorStyle.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorStyle.java @@ -26,6 +26,23 @@ public void setDefaultBackgrouds(int[] defaultBackgrouds) { } } + private int[] defaultAvatarPlaceholders = new int[]{ + 0xff509dbb, + 0xff52b3cd, + 0xff7e6eeb, + 0xffde5447, + 0xffee7e37, + 0xffed608b, + 0xff6ac53c}; + + public int[] getDefaultAvatarPlaceholders() { + return defaultAvatarPlaceholders; + } + + public void setDefaultAvatarPlaceholders(int[] defaultAvatarPlaceholders) { + this.defaultAvatarPlaceholders = defaultAvatarPlaceholders; + } + ////////////////////////// // COLORS // ////////////////////////// diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/avatar/AvatarPlaceholderDrawable.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/avatar/AvatarPlaceholderDrawable.java index 8c59427a92..225e37bc48 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/avatar/AvatarPlaceholderDrawable.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/avatar/AvatarPlaceholderDrawable.java @@ -10,6 +10,7 @@ import android.text.TextPaint; import android.util.TypedValue; +import im.actor.sdk.ActorSDK; import im.actor.sdk.R; import im.actor.sdk.util.Fonts; @@ -19,7 +20,6 @@ public class AvatarPlaceholderDrawable extends Drawable { private static float TEXT_SIZE; private float selfTextSize; private static Paint CIRCLE_PAINT; - private static int[] COLORS; private Context ctx; private String title; @@ -47,17 +47,7 @@ public AvatarPlaceholderDrawable(String title, int id, float selfTextSize, Conte } } - if (COLORS == null) { - COLORS = new int[]{ - context.getResources().getColor(R.color.placeholder_0), - context.getResources().getColor(R.color.placeholder_1), - context.getResources().getColor(R.color.placeholder_2), - context.getResources().getColor(R.color.placeholder_3), - context.getResources().getColor(R.color.placeholder_4), - context.getResources().getColor(R.color.placeholder_5), - context.getResources().getColor(R.color.placeholder_6), - }; - } + int[] colors = ActorSDK.sharedActor().style.getDefaultAvatarPlaceholders(); if (CIRCLE_PAINT == null) { CIRCLE_PAINT = new Paint(Paint.ANTI_ALIAS_FLAG); @@ -74,7 +64,7 @@ public AvatarPlaceholderDrawable(String title, int id, float selfTextSize, Conte if (id == 0) { this.color = context.getResources().getColor(R.color.placeholder_empty); } else { - this.color = COLORS[Math.abs(id) % COLORS.length]; + this.color = colors[Math.abs(id) % colors.length]; } this.title = title; From ad6b91ebf96533963cec8717cf466b25bb05744c Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Mon, 18 Jul 2016 00:38:44 +0300 Subject: [PATCH 053/414] fix(android): Fixing avatar placeholder customizations --- .../src/main/java/im/actor/Application.java | 16 ++++++++++++++++ .../content/preprocessor/ChatListProcessor.java | 10 +--------- .../conversation/view/MentionSpan.java | 10 +--------- .../sdk/controllers/dialogs/view/DialogView.java | 10 +--------- .../src/main/res/values/placeholder.xml | 7 ------- 5 files changed, 19 insertions(+), 34 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/Application.java b/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/Application.java index 2ff6af3464..4166a7ba75 100644 --- a/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/Application.java +++ b/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/Application.java @@ -1,6 +1,8 @@ package im.actor; import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.support.v7.app.ActionBar; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -20,6 +22,7 @@ import im.actor.sdk.controllers.conversation.attach.ShareMenuField; import im.actor.sdk.controllers.conversation.attach.AbsAttachFragment; import im.actor.sdk.controllers.conversation.attach.AttachFragment; +import im.actor.sdk.controllers.root.RootFragment; import im.actor.sdk.controllers.settings.ActorSettingsCategories; import im.actor.sdk.controllers.settings.ActorSettingsCategory; import im.actor.sdk.controllers.settings.ActorSettingsField; @@ -73,6 +76,19 @@ public void onConfigureActorSDK() { private class ActorSDKDelegate extends BaseActorSDKDelegate { + @Nullable + @Override + public Fragment fragmentForRoot() { + return new RootFragment() { + @Override + public void onConfigureActionBar(ActionBar actionBar) { + super.onConfigureActionBar(actionBar); + actionBar.setDisplayShowHomeEnabled(true); + actionBar.setIcon(R.drawable.ic_app_notify); + } + }; + } + @Nullable @Override public AbsAttachFragment fragmentForAttachMenu(Peer peer) { diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/preprocessor/ChatListProcessor.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/preprocessor/ChatListProcessor.java index eda0f91a18..ee473ccdb9 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/preprocessor/ChatListProcessor.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/preprocessor/ChatListProcessor.java @@ -68,15 +68,7 @@ public ChatListProcessor(Peer peer, Context context) { if (isGroup) { group = groups().get(peer.getPeerId()); } - colors = new int[]{ - context.getResources().getColor(R.color.placeholder_0), - context.getResources().getColor(R.color.placeholder_1), - context.getResources().getColor(R.color.placeholder_2), - context.getResources().getColor(R.color.placeholder_3), - context.getResources().getColor(R.color.placeholder_4), - context.getResources().getColor(R.color.placeholder_5), - context.getResources().getColor(R.color.placeholder_6), - }; + colors = ActorSDK.sharedActor().style.getDefaultAvatarPlaceholders(); } @Nullable diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/view/MentionSpan.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/view/MentionSpan.java index 99a1ed22d9..938490da37 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/view/MentionSpan.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/view/MentionSpan.java @@ -21,15 +21,7 @@ public class MentionSpan extends BaseUrlSpan { public MentionSpan(String nick, int userId, boolean hideUrlStyle) { super(nick, hideUrlStyle); this.userId = userId; - colors = new int[]{ - AndroidContext.getContext().getResources().getColor(R.color.placeholder_0), - AndroidContext.getContext().getResources().getColor(R.color.placeholder_1), - AndroidContext.getContext().getResources().getColor(R.color.placeholder_2), - AndroidContext.getContext().getResources().getColor(R.color.placeholder_3), - AndroidContext.getContext().getResources().getColor(R.color.placeholder_4), - AndroidContext.getContext().getResources().getColor(R.color.placeholder_5), - AndroidContext.getContext().getResources().getColor(R.color.placeholder_6), - }; + colors = ActorSDK.sharedActor().style.getDefaultAvatarPlaceholders(); } @Override diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/view/DialogView.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/view/DialogView.java index f3330dd89c..189620dbc1 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/view/DialogView.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/view/DialogView.java @@ -258,15 +258,7 @@ public DialogLayout buildLayout(Dialog arg, int width, int height) { counterTextPaint.setTextAlign(Paint.Align.CENTER); counterBgPaint = createFilledPaint(style.getDialogsCounterBackgroundColor()); fillPaint = createFilledPaint(Color.BLACK); - placeholderColors = new int[]{ - context.getResources().getColor(R.color.placeholder_0), - context.getResources().getColor(R.color.placeholder_1), - context.getResources().getColor(R.color.placeholder_2), - context.getResources().getColor(R.color.placeholder_3), - context.getResources().getColor(R.color.placeholder_4), - context.getResources().getColor(R.color.placeholder_5), - context.getResources().getColor(R.color.placeholder_6), - }; + placeholderColors = ActorSDK.sharedActor().style.getDefaultAvatarPlaceholders(); avatarBorder = new Paint(); avatarBorder.setStyle(Paint.Style.STROKE); avatarBorder.setAntiAlias(true); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/placeholder.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/placeholder.xml index bdcee824c7..e73bb820f4 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/placeholder.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/placeholder.xml @@ -4,11 +4,4 @@ #ffb8b8b8 - #509dbb - #52b3cd - #7e6eeb - #de5447 - #ee7e37 - #ed608b - #6ac53c \ No newline at end of file From a058ea76a39f1133624049201437d7a8f7e23e81 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Mon, 18 Jul 2016 15:27:41 +0300 Subject: [PATCH 054/414] fix(android): Fixing crashing on toolbar update when detached from activity --- .../settings/BaseActorSettingsFragment.java | 34 +++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/BaseActorSettingsFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/BaseActorSettingsFragment.java index 9e1f64a049..6594a69b72 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/BaseActorSettingsFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/BaseActorSettingsFragment.java @@ -1,5 +1,6 @@ package im.actor.sdk.controllers.settings; +import android.app.Activity; import android.app.AlertDialog; import android.content.ClipData; import android.content.ClipboardManager; @@ -66,6 +67,7 @@ public abstract class BaseActorSettingsFragment extends BaseFragment implements IActorSettingsFragment { + private boolean animateToolbar = true; private int baseColor; private AvatarView avatarView; protected SharedPreferences shp; @@ -74,10 +76,22 @@ public abstract class BaseActorSettingsFragment extends BaseFragment implements private boolean noEmails = false; private HeaderViewRecyclerAdapter wallpaperAdapter; + public BaseActorSettingsFragment() { + setHasOptionsMenu(true); + } + + public boolean isAnimateToolbar() { + return animateToolbar; + } + + public void setAnimateToolbar(boolean animateToolbar) { + this.animateToolbar = animateToolbar; + } + @Override public void onCreate(Bundle saveInstance) { super.onCreate(saveInstance); - setHasOptionsMenu(true); + animateToolbar = saveInstance.getBoolean("animateToolbar", true); } @Override @@ -561,6 +575,7 @@ public void onClick(View view) { } view.findViewById(R.id.avatarContainer).setBackgroundColor(style.getToolBarColor()); + avatarView = (AvatarView) view.findViewById(R.id.avatar); avatarView.init(Screen.dp(96), 44); avatarView.bind(users().get(myUid())); @@ -684,8 +699,17 @@ private void addFields(FrameLayout container, ArrayList fiel } private void updateActionBar(int offset) { - + if (!animateToolbar) { + return; + } + Activity activity = getActivity(); + if (!(activity instanceof BaseActivity)) { + return; + } ActionBar bar = ((BaseActivity) getActivity()).getSupportActionBar(); + if (bar == null) { + return; + } int fullColor = baseColor; ActorStyle style = ActorSDK.sharedActor().style; if (style.getToolBarColor() != 0) { @@ -747,6 +771,12 @@ public boolean onOptionsItemSelected(MenuItem item) { return super.onOptionsItemSelected(item); } + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putBoolean("animateToolbar", animateToolbar); + } + @Override public View getBeforeNickSettingsView() { return null; From 89adbc5a4f9966ad9edaf3c5757f95d6ef06e05f Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Mon, 18 Jul 2016 15:45:42 +0300 Subject: [PATCH 055/414] fix(android): Fixing crash in settings activity --- .../sdk/controllers/settings/BaseActorSettingsFragment.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/BaseActorSettingsFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/BaseActorSettingsFragment.java index 6594a69b72..e75a21bff1 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/BaseActorSettingsFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/BaseActorSettingsFragment.java @@ -91,7 +91,9 @@ public void setAnimateToolbar(boolean animateToolbar) { @Override public void onCreate(Bundle saveInstance) { super.onCreate(saveInstance); - animateToolbar = saveInstance.getBoolean("animateToolbar", true); + if (saveInstance != null) { + animateToolbar = saveInstance.getBoolean("animateToolbar", true); + } } @Override From 6ad22fc4934a52c7cdfe8d708924f2345ca80b20 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Tue, 19 Jul 2016 15:14:17 +0300 Subject: [PATCH 056/414] fix(core): Fixing update reordering issue that causes wrong counters --- .../sequence/internal/GetDiffCombiner.java | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/sequence/internal/GetDiffCombiner.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/sequence/internal/GetDiffCombiner.java index d1330a635f..c132ada335 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/sequence/internal/GetDiffCombiner.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/sequence/internal/GetDiffCombiner.java @@ -1,7 +1,11 @@ package im.actor.core.modules.sequence.internal; +import java.util.ArrayList; import java.util.List; +import im.actor.core.api.ApiDialogGroup; +import im.actor.core.api.ApiDialogShort; +import im.actor.core.api.updates.UpdateChatGroupsChanged; import im.actor.core.api.updates.UpdateCountersChanged; import im.actor.core.api.updates.UpdateMessage; import im.actor.core.api.updates.UpdateMessageRead; @@ -19,6 +23,7 @@ public class GetDiffCombiner { public static CombinedDifference buildDiff(List updates) { CombinedDifference res = new CombinedDifference(); + UpdateChatGroupsChanged chatGroupsChanged = null; for (Update u : updates) { if (u instanceof UpdateMessage) { res.putMessage((UpdateMessage) u); @@ -37,10 +42,32 @@ public static CombinedDifference buildDiff(List updates) { res.putReadByMe(convert(readByMe.getPeer()), readByMe.getStartDate(), counter); } else if (u instanceof UpdateCountersChanged) { // Ignore + } else if (u instanceof UpdateChatGroupsChanged) { + chatGroupsChanged = (UpdateChatGroupsChanged) u; } else { res.getOtherUpdates().add(u); } } + + // Handling reordering of updates + if (chatGroupsChanged != null) { + ArrayList dialogs = new ArrayList<>(); + for (ApiDialogGroup dg : chatGroupsChanged.getDialogs()) { + ArrayList dialogShorts = new ArrayList<>(); + for (ApiDialogShort ds : dg.getDialogs()) { + CombinedDifference.ReadByMeValue val = res.getReadByMe().get(convert(ds.getPeer())); + if (val != null) { + dialogShorts.add(new ApiDialogShort(ds.getPeer(), + val.getCounter(), ds.getDate())); + } else { + dialogShorts.add(ds); + } + } + dialogs.add(new ApiDialogGroup(dg.getTitle(), dg.getKey(), dialogShorts)); + } + res.getOtherUpdates().add(new UpdateChatGroupsChanged(dialogs)); + } + return res; } } \ No newline at end of file From 252beb7f47d0af51371766bf4f4af98cdd68ec43 Mon Sep 17 00:00:00 2001 From: rockjam Date: Tue, 19 Jul 2016 16:09:35 +0300 Subject: [PATCH 057/414] chore(server): update sbt 0.13.11 -> 0.13.12 --- actor-server/project/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actor-server/project/build.properties b/actor-server/project/build.properties index 43b8278c68..35c88bab7d 100644 --- a/actor-server/project/build.properties +++ b/actor-server/project/build.properties @@ -1 +1 @@ -sbt.version=0.13.11 +sbt.version=0.13.12 From f27246eb19f6c4b81701e0248ad604b17e5cbb58 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Tue, 19 Jul 2016 17:48:10 +0300 Subject: [PATCH 058/414] feat(android): Adding group's short name edition --- .../android-sdk/src/main/AndroidManifest.xml | 6 + .../controllers/group/AddMemberActivity.java | 3 +- .../controllers/group/GroupAdminActivity.java | 19 +++ .../controllers/group/GroupAdminFragment.java | 158 ++++++++++++++++++ .../controllers/group/GroupInfoFragment.java | 52 ++++-- .../res/layout/fragment_administration.xml | 156 +++++++++++++++++ .../main/res/layout/fragment_group_header.xml | 11 ++ .../src/main/res/values/ui_text.xml | 6 + .../main/java/im/actor/core/Messenger.java | 15 +- .../main/java/im/actor/core/entity/Group.java | 37 +++- .../core/modules/groups/GroupsModule.java | 9 + .../java/im/actor/core/viewmodel/GroupVM.java | 69 ++++++++ 12 files changed, 523 insertions(+), 18 deletions(-) create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupAdminActivity.java create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupAdminFragment.java create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_administration.xml diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/AndroidManifest.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/AndroidManifest.xml index 8582179c9a..84dc7a8261 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/AndroidManifest.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/AndroidManifest.xml @@ -151,6 +151,12 @@ android:theme="@style/CommonActivityTheme" android:windowSoftInputMode="adjustNothing|stateAlwaysHidden" /> + + { + if (!isPublic) { + isPublic = true; + publicRadio.setChecked(true); + privateRadio.setChecked(false); + publicLinkContainer.setVisibility(View.VISIBLE); + publicShadowTop.setVisibility(View.VISIBLE); + publicShadowBottom.setVisibility(View.VISIBLE); + publicShortName.setText(groupVM.getShortName().get()); + } + }; + View.OnClickListener privateClick = view -> { + if (isPublic) { + isPublic = false; + publicRadio.setChecked(false); + privateRadio.setChecked(true); + publicLinkContainer.setVisibility(View.GONE); + publicShadowTop.setVisibility(View.GONE); + publicShadowBottom.setVisibility(View.GONE); + publicShortName.setText(null); + } + }; + publicRadio.setOnClickListener(publicClick); + privateRadio.setOnClickListener(privateClick); + + return res; + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + super.onCreateOptionsMenu(menu, inflater); + inflater.inflate(R.menu.next, menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == R.id.next) { + if (isPublic) { + String nShortName = publicShortName.getText().toString().trim(); + if (nShortName.length() == 0) { + finishActivity(); + return true; + } + if (nShortName.equals(groupVM.getShortName().get())) { + return true; + } + execute(messenger().editGroupShortName(groupVM.getId(), nShortName).then(r -> { + finishActivity(); + }).failure(e -> { + Toast.makeText(getActivity(), "Unable to change group short name", Toast.LENGTH_SHORT).show(); + })); + } else { + if (groupVM.getShortName().get() == null) { + finishActivity(); + return true; + } else { + execute(messenger().editGroupShortName(groupVM.getId(), null).then(r -> { + finishActivity(); + }).failure(e -> { + Toast.makeText(getActivity(), "Unable to change group short name", Toast.LENGTH_SHORT).show(); + })); + } + } + return true; + } + return super.onOptionsItemSelected(item); + } +} diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java index 0de7b452e1..fd7e43b2b1 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java @@ -31,6 +31,8 @@ import im.actor.core.viewmodel.UserPhone; import im.actor.core.viewmodel.UserVM; import im.actor.runtime.actors.messages.Void; +import im.actor.runtime.mvvm.Value; +import im.actor.runtime.mvvm.ValueDoubleChangedListener; import im.actor.sdk.ActorSDK; import im.actor.sdk.ActorSDKLauncher; import im.actor.sdk.ActorStyle; @@ -111,9 +113,11 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa TextView aboutTV = (TextView) header.findViewById(R.id.about); View aboutCont = header.findViewById(R.id.aboutContainer); + TextView addMember = (TextView) header.findViewById(R.id.addMemberAction); TextView members = (TextView) header.findViewById(R.id.viewMembersAction); TextView leaveAction = (TextView) header.findViewById(R.id.leaveAction); + TextView administrationAction = (TextView) header.findViewById(R.id.administrationAction); View descriptionContainer = header.findViewById(R.id.descriptionContainer); SwitchCompat isNotificationsEnabled = (SwitchCompat) header.findViewById(R.id.enableNotifications); @@ -137,8 +141,10 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa ((TextView) header.findViewById(R.id.addMemberAction)) .setTextColor(style.getTextPrimaryColor()); members.setTextColor(style.getTextPrimaryColor()); + administrationAction.setTextColor(style.getTextPrimaryColor()); leaveAction.setTextColor(style.getTextDangerColor()); + if (groupVM.getGroupType() == GroupType.CHANNEL) { leaveAction.setText(R.string.group_leave_channel); } else { @@ -188,23 +194,23 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa }); addMember.setOnClickListener(view -> { startActivity(new Intent(getActivity(), AddMemberActivity.class) - .putExtra("GROUP_ID", chatId)); + .putExtra(Intents.EXTRA_GROUP_ID, chatId)); }); - // Leave - leaveAction.setOnClickListener(view1 -> { - new AlertDialog.Builder(getActivity()) - .setMessage(getString(R.string.alert_leave_group_message).replace("%1$s", - groupVM.getName().get())) - .setPositiveButton(R.string.alert_leave_group_yes, (dialog2, which) -> { - execute(messenger().leaveGroup(chatId)); - }) - .setNegativeButton(R.string.dialog_cancel, null) - .show() - .setCanceledOnTouchOutside(true); + // Administration + bind(groupVM.getIsCanEditAdministration(), groupVM.getIsCanEditShortName(), (canEditAdministration, valueModel, canEditShortName, valueModel2) -> { + if (canEditAdministration || canEditShortName) { + administrationAction.setVisibility(View.VISIBLE); + } else { + administrationAction.setVisibility(View.GONE); + } + }); + administrationAction.setOnClickListener(view -> { + startActivity(new Intent(getActivity(), GroupAdminActivity.class) + .putExtra(Intents.EXTRA_GROUP_ID, chatId)); }); - // Members + // Async Members // Showing member only when members available and async members is enabled bind(groupVM.getIsCanViewMembers(), groupVM.getIsAsyncMembers(), (canViewMembers, vm1, isAsync, vm2) -> { if (canViewMembers) { @@ -224,6 +230,19 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa .putExtra(Intents.EXTRA_GROUP_ID, groupVM.getId())); }); + // Leave + leaveAction.setOnClickListener(view1 -> { + new AlertDialog.Builder(getActivity()) + .setMessage(getString(R.string.alert_leave_group_message).replace("%1$s", + groupVM.getName().get())) + .setPositiveButton(R.string.alert_leave_group_yes, (dialog2, which) -> { + execute(messenger().leaveGroup(chatId)); + }) + .setNegativeButton(R.string.dialog_cancel, null) + .show() + .setCanceledOnTouchOutside(true); + }); + listView.addHeaderView(header, null, false); // @@ -300,6 +319,11 @@ public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCoun getActivity().invalidateOptionsMenu(); }); + // Menu + bind(groupVM.getIsCanEditInfo(), canEditInfo -> { + getActivity().invalidateOptionsMenu(); + }); + return res; } @@ -368,7 +392,7 @@ public void onError(Exception e) { @Override public void onCreateOptionsMenu(Menu menu, MenuInflater menuInflater) { super.onCreateOptionsMenu(menu, menuInflater); - if (groupVM.isMember().get()) { + if (groupVM.getIsCanEditInfo().get()) { MenuItem menuItem = menu.add(Menu.NONE, R.id.edit, Menu.NONE, R.string.actor_menu_edit); menuItem.setIcon(R.drawable.ic_edit_white_24dp); menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_administration.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_administration.xml new file mode 100644 index 0000000000..aa83f0b0cf --- /dev/null +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_administration.xml @@ -0,0 +1,156 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_group_header.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_group_header.xml index 42b25853d7..ca8893c310 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_group_header.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_group_header.xml @@ -160,6 +160,17 @@ android:text="@string/group_add_member" android:textSize="16sp" /> + + Add a member Leave group Leave channel + Administration + + Public Group + Public groups can be found in search, anyone can join them. + Private Group + Private Group can only be joined via personal invitation. You are not a member of this group diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java index fcf1682d25..928a183ad9 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java @@ -1419,10 +1419,23 @@ public Command editGroupTheme(final int gid, final String theme) { */ @NotNull @ObjectiveCName("editGroupAboutWithGid:withAbout:") - public Promise editGroupAbout(final int gid, final String about) { + public Promise editGroupAbout(int gid, String about) { return modules.getGroupsModule().editAbout(gid, about); } + /** + * Edit group's short name + * + * @param gid group's id + * @param shortName new group short name + * @return Promise for void + */ + @NotNull + @ObjectiveCName("editGroupShortNameWithGid:withAbout:") + public Promise editGroupShortName(int gid, String shortName) { + return modules.getGroupsModule().editShortName(gid, shortName); + } + /** * Change group avatar * diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Group.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Group.java index 84f5c7f899..84919daf2d 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Group.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Group.java @@ -87,6 +87,14 @@ public class Group extends WrapperExtEntity implements K private boolean isSharedHistory; @Property("readonly, nonatomic") private boolean isCanEditInfo; + @Property("readonly, nonatomic") + private boolean isCanEditAdministration; + @Property("readonly, nonatomic") + private boolean isCanEditShortName; + @Property("readonly, nonatomic") + private boolean isCanViewAdmins; + @Property("readonly, nonatomic") + private boolean isCanEditAdmins; @Property("readonly, nonatomic") private boolean haveExtension; @@ -203,6 +211,22 @@ public boolean isHaveExtension() { return haveExtension; } + public boolean isCanEditShortName() { + return isCanEditShortName; + } + + public boolean isCanEditAdministration() { + return isCanEditAdministration; + } + + public boolean isCanViewAdmins() { + return isCanViewAdmins; + } + + public boolean isCanEditAdmins() { + return isCanEditAdmins; + } + public Group updateExt(@Nullable ApiGroupFull ext) { return new Group(getWrapped(), ext); } @@ -895,6 +919,11 @@ protected void applyWrapped(@NotNull ApiGroup wrapped, @Nullable ApiGroupFull ex this.isCanInviteMembers = ext.canViewMembers() != null ? ext.canViewMembers() : true; this.isSharedHistory = ext.isSharedHistory() != null ? ext.isSharedHistory() : false; this.isCanEditInfo = ext.canEditGroupInfo() != null ? ext.canEditGroupInfo() : false; + this.isCanEditShortName = ext.canEditShortName() != null ? ext.canEditShortName() : false; + this.isCanEditAdministration = ext.canEditAdminSettings() != null ? ext.canEditAdminSettings() : false; + this.isCanViewAdmins = ext.canViewAdminList() != null ? ext.canViewAdminList() : false; + this.isCanEditAdmins = ext.canEditAdminList() != null ? ext.canEditAdminList() : false; + this.members = new ArrayList<>(); for (ApiMember m : ext.getMembers()) { this.members.add(new GroupMember(m.getUid(), m.getInviterUid(), m.getDate(), @@ -905,13 +934,17 @@ protected void applyWrapped(@NotNull ApiGroup wrapped, @Nullable ApiGroupFull ex this.ownerId = 0; this.about = null; this.topic = null; + this.shortName = null; + this.members = new ArrayList<>(); this.isAsyncMembers = false; this.isCanViewMembers = false; this.isCanInviteMembers = false; this.isSharedHistory = false; - this.members = new ArrayList<>(); this.isCanEditInfo = false; - this.shortName = null; + this.isCanEditShortName = false; + this.isCanEditAdministration = false; + this.isCanViewAdmins = false; + this.isCanEditAdmins = false; } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsModule.java index 2b5309f228..57627ed821 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsModule.java @@ -15,6 +15,7 @@ import im.actor.core.api.ApiUserOutPeer; import im.actor.core.api.rpc.RequestCreateGroup; import im.actor.core.api.rpc.RequestEditGroupAbout; +import im.actor.core.api.rpc.RequestEditGroupShortName; import im.actor.core.api.rpc.RequestEditGroupTitle; import im.actor.core.api.rpc.RequestEditGroupTopic; import im.actor.core.api.rpc.RequestGetGroupInviteUrl; @@ -230,6 +231,14 @@ public Promise editAbout(final int gid, final String about) { .flatMap(r -> updates().waitForUpdate(r.getSeq())); } + public Promise editShortName(final int gid, final String shortName) { + return getGroups().getValueAsync(gid) + .flatMap(group -> + api(new RequestEditGroupShortName(new ApiGroupOutPeer(group.getGroupId(), group.getAccessHash()), + shortName))) + .flatMap(r -> updates().waitForUpdate(r.getSeq())); + } + public Promise loadMembers(int gid, int limit, byte[] next) { return getGroups().getValueAsync(gid) .flatMap(group -> diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java index 0e8013f07c..d9bc3b9179 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java @@ -69,6 +69,21 @@ public class GroupVM extends BaseValueModel { @NotNull @Property("nonatomic, readonly") private BooleanValueModel isCanEditInfo; + @NotNull + @Property("nonatomic, readonly") + private BooleanValueModel isHistoryShared; + @NotNull + @Property("nonatomic, readonly") + private BooleanValueModel isCanEditAdministration; + @NotNull + @Property("nonatomic, readonly") + private BooleanValueModel isCanEditShortName; + @NotNull + @Property("nonatomic, readonly") + private BooleanValueModel isCanEditAdmins; + @NotNull + @Property("nonatomic, readonly") + private BooleanValueModel isCanViewAdmins; @NotNull @@ -109,6 +124,12 @@ public GroupVM(@NotNull Group rawObj) { this.isCanInviteMembers = new BooleanValueModel("group." + groupId + ".can_invite_members", rawObj.isCanInviteMembers()); this.isCanEditInfo = new BooleanValueModel("group." + groupId + ".can_edit_info", rawObj.isCanEditInfo()); this.isAsyncMembers = new BooleanValueModel("group." + groupId + ".isAsyncMembers", rawObj.isAsyncMembers()); + this.isCanEditAdministration = new BooleanValueModel("group." + groupId + ".isCanEditAdministration", rawObj.isCanEditAdministration()); + this.isCanEditShortName = new BooleanValueModel("group." + groupId + ".isCanEditShortName", rawObj.isCanEditShortName()); + this.isHistoryShared = new BooleanValueModel("group." + groupId + ".isHistoryShared", rawObj.isSharedHistory()); + this.isCanEditAdmins = new BooleanValueModel("group." + groupId + ".isCanEditAdmins", rawObj.isCanEditAdmins()); + this.isCanViewAdmins = new BooleanValueModel("group." + groupId + ".isCanViewAdmins", rawObj.isCanViewAdmins()); + this.ownerId = new IntValueModel("group." + groupId + ".membersCount", rawObj.getOwnerId()); this.members = new ValueModel<>("group." + groupId + ".members", new HashSet<>(rawObj.getMembers())); this.presence = new ValueModel<>("group." + groupId + ".presence", 0); @@ -182,6 +203,17 @@ public StringValueModel getTheme() { return theme; } + /** + * Get Short Name Model + * + * @return Value Model of String + */ + @NotNull + @ObjectiveCName("getShortNameModel") + public StringValueModel getShortName() { + return shortName; + } + /** * Get membership Value Model * @@ -260,6 +292,39 @@ public BooleanValueModel getIsAsyncMembers() { return isAsyncMembers; } + /** + * Is history shared in this group + * + * @return is history shared model + */ + @NotNull + @ObjectiveCName("getIsHistorySharedModel") + public BooleanValueModel getIsHistoryShared() { + return isHistoryShared; + } + + /** + * Is current user can edit administration settings + * + * @return is can edit administration model + */ + @NotNull + @ObjectiveCName("getIsCanEditAdministrationModel") + public BooleanValueModel getIsCanEditAdministration() { + return isCanEditAdministration; + } + + /** + * Is current user can edit short name of a group + * + * @return is current user can edit short name + */ + @NotNull + @ObjectiveCName("getIsCanEditShortNameModel") + public BooleanValueModel getIsCanEditShortName() { + return isCanEditShortName; + } + /** * Get Group owner user id model * @@ -358,6 +423,10 @@ protected void updateValues(@NotNull Group rawObj) { isChanged |= isCanEditInfo.change(rawObj.isCanEditInfo()); isChanged |= shortName.change(rawObj.getShortName()); isChanged |= isAsyncMembers.change(rawObj.isAsyncMembers()); + isChanged |= isHistoryShared.change(rawObj.isSharedHistory()); + isChanged |= isCanEditAdministration.change(rawObj.isCanEditAdministration()); + isChanged |= isCanEditAdmins.change(rawObj.isCanEditAdmins()); + isChanged |= isCanViewAdmins.change(rawObj.isCanViewAdmins()); if (isChanged) { notifyIfNeeded(); From 5ca51bde0c5ae4c41043f8d3d6bcc0c97aaa8d84 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Tue, 19 Jul 2016 18:04:50 +0300 Subject: [PATCH 059/414] feat(android): Implemented showing short name of the group --- .../controllers/group/GroupInfoFragment.java | 24 ++++++++++++--- .../main/res/layout/fragment_group_header.xml | 30 +++++++++++++++++-- 2 files changed, 48 insertions(+), 6 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java index fd7e43b2b1..225efd1424 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java @@ -33,6 +33,7 @@ import im.actor.runtime.actors.messages.Void; import im.actor.runtime.mvvm.Value; import im.actor.runtime.mvvm.ValueDoubleChangedListener; +import im.actor.runtime.mvvm.ValueDoubleListener; import im.actor.sdk.ActorSDK; import im.actor.sdk.ActorSDKLauncher; import im.actor.sdk.ActorStyle; @@ -112,7 +113,9 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa avatarView.init(Screen.dp(48), 22); TextView aboutTV = (TextView) header.findViewById(R.id.about); - View aboutCont = header.findViewById(R.id.aboutContainer); + View shortNameCont = header.findViewById(R.id.shortNameContainer); + TextView shortNameView = (TextView) header.findViewById(R.id.shortName); + TextView shortLinkView = (TextView) header.findViewById(R.id.shortNameLink); TextView addMember = (TextView) header.findViewById(R.id.addMemberAction); TextView members = (TextView) header.findViewById(R.id.viewMembersAction); @@ -128,8 +131,9 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa header.findViewById(R.id.avatarContainer).setBackgroundColor(style.getToolBarColor()); title.setTextColor(style.getProfileTitleColor()); subtitle.setTextColor(style.getProfileSubtitleColor()); - aboutTV.setTextColor(style.getTextPrimaryColor()); + shortNameView.setTextColor(style.getTextPrimaryColor()); + shortLinkView.setTextColor(style.getTextSecondaryColor()); // settingsHeaderText.setTextColor(style.getSettingsCategoryTextColor()); ((TintImageView) header.findViewById(R.id.settings_notification_icon)) @@ -170,9 +174,21 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa }); // About - bind(groupVM.getOwnerId(), groupVM.getAbout(), (ownerId, valueModel, about, valueModel2) -> { + bind(groupVM.getAbout(), (about) -> { aboutTV.setText(about); - descriptionContainer.setVisibility(about != null ? View.VISIBLE : View.GONE); + aboutTV.setVisibility(about != null ? View.VISIBLE : View.GONE); + }); + bind(groupVM.getShortName(), shortName -> { + if (shortName != null) { + shortNameView.setText("@" + shortName); + shortLinkView.setText("actor.im/join/" + shortName); + } + shortNameCont.setVisibility(shortName != null ? View.VISIBLE : View.GONE); + }); + bind(groupVM.getAbout(), groupVM.getShortName(), (about, shortName) -> { + descriptionContainer.setVisibility(about != null || shortName != null + ? View.VISIBLE + : View.GONE); }); // Notifications diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_group_header.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_group_header.xml index ca8893c310..c4ad763fc6 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_group_header.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_group_header.xml @@ -75,7 +75,6 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="12dp" - android:background="@drawable/clickable_background" android:gravity="top" android:minHeight="48dp"> @@ -91,14 +90,41 @@ android:id="@+id/about" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_centerVertical="true" android:gravity="center_vertical" + android:minHeight="48dp" android:paddingBottom="8dp" android:paddingLeft="72dp" android:paddingRight="8dp" android:paddingTop="8dp" android:textSize="16sp" /> + + + + + + + + Date: Tue, 19 Jul 2016 18:18:15 +0300 Subject: [PATCH 060/414] feat(scheme): canInviteViaLink, canDelete, canLeave permissions. Method for deleting groups. --- actor-sdk/sdk-api/actor.json | 183 ++++++++++++++++++ .../models/im/actor/api/scheme.mps | 152 +++++++++++++++ 2 files changed, 335 insertions(+) diff --git a/actor-sdk/sdk-api/actor.json b/actor-sdk/sdk-api/actor.json index 241fe5e581..0026329354 100644 --- a/actor-sdk/sdk-api/actor.json +++ b/actor-sdk/sdk-api/actor.json @@ -8225,6 +8225,24 @@ "argument": "canEditAdminSettings", "category": "full", "description": " If not set only owner can edit admin settings" + }, + { + "type": "reference", + "argument": "canInviteViaLink", + "category": "full", + "description": " If user can invite via link, default is false" + }, + { + "type": "reference", + "argument": "canDelete", + "category": "full", + "description": " If user can delete this group, default is false" + }, + { + "type": "reference", + "argument": "canLeave", + "category": "hidden", + "description": " If user can leave this group, default is true" } ], "expandable": "true", @@ -8373,6 +8391,30 @@ }, "id": 18, "name": "canEditAdminSettings" + }, + { + "type": { + "type": "opt", + "childType": "bool" + }, + "id": 19, + "name": "canInviteViaLink" + }, + { + "type": { + "type": "opt", + "childType": "bool" + }, + "id": 20, + "name": "canDelete" + }, + { + "type": { + "type": "opt", + "childType": "bool" + }, + "id": 21, + "name": "canLeave" } ] } @@ -9136,6 +9178,117 @@ ] } }, + { + "type": "update", + "content": { + "name": "GroupCanInviteViaLink", + "header": 2646, + "doc": [ + "Update about can invite via link changed", + { + "type": "reference", + "argument": "groupId", + "category": "full", + "description": " Group Id" + }, + { + "type": "reference", + "argument": "canInviteViaLink", + "category": "full", + "description": " Can Invite Via link" + } + ], + "attributes": [ + { + "type": { + "type": "alias", + "childType": "groupId" + }, + "id": 1, + "name": "groupId" + }, + { + "type": "bool", + "id": 2, + "name": "canInviteViaLink" + } + ] + } + }, + { + "type": "update", + "content": { + "name": "GroupCanLeaveChanged", + "header": 2647, + "doc": [ + "Update about can leave changed", + { + "type": "reference", + "argument": "groupId", + "category": "full", + "description": " Group Id" + }, + { + "type": "reference", + "argument": "canLeaveChanged", + "category": "full", + "description": " Can leave changed" + } + ], + "attributes": [ + { + "type": { + "type": "alias", + "childType": "groupId" + }, + "id": 1, + "name": "groupId" + }, + { + "type": "bool", + "id": 2, + "name": "canLeaveChanged" + } + ] + } + }, + { + "type": "update", + "content": { + "name": "GroupCanDeleteChanged", + "header": 2648, + "doc": [ + "Update about can delete changed", + { + "type": "reference", + "argument": "groupId", + "category": "full", + "description": " Group Id" + }, + { + "type": "reference", + "argument": "canDeleteChanged", + "category": "full", + "description": " Can delete changed" + } + ], + "attributes": [ + { + "type": { + "type": "alias", + "childType": "groupId" + }, + "id": 1, + "name": "groupId" + }, + { + "type": "bool", + "id": 2, + "name": "canDeleteChanged" + } + ] + } + }, { "type": "update", "content": { @@ -10580,6 +10733,36 @@ ] } }, + { + "type": "rpc", + "content": { + "name": "DeleteGroup", + "header": 2795, + "response": { + "type": "reference", + "name": "Seq" + }, + "doc": [ + "Delete Group", + { + "type": "reference", + "argument": "groupPeer", + "category": "full", + "description": "Group's peer" + } + ], + "attributes": [ + { + "type": { + "type": "struct", + "childType": "GroupOutPeer" + }, + "id": 1, + "name": "groupPeer" + } + ] + } + }, { "type": "comment", "content": "Invite" diff --git a/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps b/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps index 37130204ab..8099762fb1 100644 --- a/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps +++ b/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps @@ -7256,6 +7256,27 @@ + + + + + + + + + + + + + + + + + + + + + @@ -7339,6 +7360,20 @@ + + + + + + + + + + + + + + @@ -7963,6 +7998,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -9191,6 +9319,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + From 11f8897c4476fb4e48f981291734325cf6e7f383 Mon Sep 17 00:00:00 2001 From: rockjam Date: Tue, 19 Jul 2016 18:37:17 +0300 Subject: [PATCH 061/414] fix(server:files): unsafe file names on file download --- .../server/file/local/FileStorageOperations.scala | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/actor-server/actor-fs-adapters/src/main/scala/im/actor/server/file/local/FileStorageOperations.scala b/actor-server/actor-fs-adapters/src/main/scala/im/actor/server/file/local/FileStorageOperations.scala index 7ffe56f466..7d1d341d82 100644 --- a/actor-server/actor-fs-adapters/src/main/scala/im/actor/server/file/local/FileStorageOperations.scala +++ b/actor-server/actor-fs-adapters/src/main/scala/im/actor/server/file/local/FileStorageOperations.scala @@ -6,14 +6,15 @@ import akka.actor.ActorSystem import akka.event.Logging import akka.http.scaladsl.util.FastFuture import akka.stream.Materializer -import akka.stream.scaladsl.{ FileIO, Source } +import akka.stream.scaladsl.{FileIO, Source} import akka.util.ByteString -import better.files.{ File, _ } +import better.files.{File, _} import im.actor.server.db.DbExtension +import im.actor.server.file.UnsafeFileName import im.actor.server.persist.files.FileRepo -import scala.concurrent.{ ExecutionContext, Future, blocking } -import scala.util.{ Failure, Success } +import scala.concurrent.{ExecutionContext, Future, blocking} +import scala.util.{Failure, Success} trait FileStorageOperations extends LocalUploadKeyImplicits { @@ -116,7 +117,7 @@ trait FileStorageOperations extends LocalUploadKeyImplicits { protected def getFileData(file: File): Future[ByteString] = FileIO.fromPath(file.path).runFold(ByteString.empty)(_ ++ _) - protected def getFileName(name: String) = if (name.trim.isEmpty) "file" else name + protected def getFileName(name: String) = if (name.trim.isEmpty) "file" else UnsafeFileName(name).safe protected def fileDirectory(fileId: Long): File = file"$storageLocation/file_${fileId}" From a009826edd96e414e1c55d81d90a4447dc81ee10 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Tue, 19 Jul 2016 20:57:17 +0300 Subject: [PATCH 062/414] ref(android): GroupAdminActivity -> GroupTypeActivity --- .../android-sdk/src/main/AndroidManifest.xml | 2 +- .../src/main/java/im/actor/sdk/ActorSDK.java | 25 +++++++++++++++++++ .../controllers/group/GroupInfoFragment.java | 15 ++++++----- ...inActivity.java => GroupTypeActivity.java} | 4 +-- ...inFragment.java => GroupTypeFragment.java} | 8 +++--- 5 files changed, 41 insertions(+), 13 deletions(-) rename actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/{GroupAdminActivity.java => GroupTypeActivity.java} (78%) rename actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/{GroupAdminFragment.java => GroupTypeFragment.java} (96%) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/AndroidManifest.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/AndroidManifest.xml index 84dc7a8261..8bf892292d 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/AndroidManifest.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/AndroidManifest.xml @@ -152,7 +152,7 @@ android:windowSoftInputMode="adjustNothing|stateAlwaysHidden" /> diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorSDK.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorSDK.java index 3ba0c4468b..17f3a902cf 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorSDK.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorSDK.java @@ -14,6 +14,7 @@ import com.facebook.imagepipeline.core.ImagePipelineConfig; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.Locale; import java.util.TimeZone; @@ -132,6 +133,11 @@ public class ActorSDK { * Invite url */ private String inviteUrl = "https://actor.im/dl"; + /** + * Invite url + */ + @Nullable + private String groupInvitePrefix = "actor.im/join/"; /** * Help phone */ @@ -519,6 +525,25 @@ public void setAppName(String appName) { this.appName = appName; } + /** + * Getting Group Invite Prefix + * + * @return group invite prefix + */ + @Nullable + public String getGroupInvitePrefix() { + return groupInvitePrefix; + } + + /** + * Setting Group Invite Prefix + * + * @param groupInvitePrefix group invite prefix + */ + public void setGroupInvitePrefix(@Nullable String groupInvitePrefix) { + this.groupInvitePrefix = groupInvitePrefix; + } + /** * Getting Push Registration Id * diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java index 225efd1424..68d40148ac 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java @@ -31,9 +31,6 @@ import im.actor.core.viewmodel.UserPhone; import im.actor.core.viewmodel.UserVM; import im.actor.runtime.actors.messages.Void; -import im.actor.runtime.mvvm.Value; -import im.actor.runtime.mvvm.ValueDoubleChangedListener; -import im.actor.runtime.mvvm.ValueDoubleListener; import im.actor.sdk.ActorSDK; import im.actor.sdk.ActorSDKLauncher; import im.actor.sdk.ActorStyle; @@ -148,7 +145,6 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa administrationAction.setTextColor(style.getTextPrimaryColor()); leaveAction.setTextColor(style.getTextDangerColor()); - if (groupVM.getGroupType() == GroupType.CHANNEL) { leaveAction.setText(R.string.group_leave_channel); } else { @@ -181,7 +177,14 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa bind(groupVM.getShortName(), shortName -> { if (shortName != null) { shortNameView.setText("@" + shortName); - shortLinkView.setText("actor.im/join/" + shortName); + + String prefix = ActorSDK.sharedActor().getGroupInvitePrefix(); + if (prefix != null) { + shortLinkView.setText(prefix + shortName); + shortLinkView.setVisibility(View.VISIBLE); + } else { + shortLinkView.setVisibility(View.GONE); + } } shortNameCont.setVisibility(shortName != null ? View.VISIBLE : View.GONE); }); @@ -222,7 +225,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa } }); administrationAction.setOnClickListener(view -> { - startActivity(new Intent(getActivity(), GroupAdminActivity.class) + startActivity(new Intent(getActivity(), GroupTypeActivity.class) .putExtra(Intents.EXTRA_GROUP_ID, chatId)); }); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupAdminActivity.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupTypeActivity.java similarity index 78% rename from actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupAdminActivity.java rename to actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupTypeActivity.java index 3aab5e0492..73e7173824 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupAdminActivity.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupTypeActivity.java @@ -5,14 +5,14 @@ import im.actor.sdk.controllers.Intents; import im.actor.sdk.controllers.activity.BaseFragmentActivity; -public class GroupAdminActivity extends BaseFragmentActivity { +public class GroupTypeActivity extends BaseFragmentActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (savedInstanceState == null) { - showFragment(GroupAdminFragment.create( + showFragment(GroupTypeFragment.create( getIntent().getIntExtra(Intents.EXTRA_GROUP_ID, 0)), false); } } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupAdminFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupTypeFragment.java similarity index 96% rename from actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupAdminFragment.java rename to actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupTypeFragment.java index 770783da07..8f920d2f17 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupAdminFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupTypeFragment.java @@ -20,12 +20,12 @@ import static im.actor.sdk.util.ActorSDKMessenger.messenger; -public class GroupAdminFragment extends BaseFragment { +public class GroupTypeFragment extends BaseFragment { - public static GroupAdminFragment create(int groupId) { + public static GroupTypeFragment create(int groupId) { Bundle bundle = new Bundle(); bundle.putInt("groupId", groupId); - GroupAdminFragment editFragment = new GroupAdminFragment(); + GroupTypeFragment editFragment = new GroupTypeFragment(); editFragment.setArguments(bundle); return editFragment; } @@ -34,7 +34,7 @@ public static GroupAdminFragment create(int groupId) { private GroupVM groupVM; private boolean isPublic; - public GroupAdminFragment() { + public GroupTypeFragment() { setTitle(R.string.group_title); setRootFragment(true); setHomeAsUp(true); From 9c2b264e413516781e5057b7e4a0c94d195f6f7a Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Tue, 19 Jul 2016 23:22:50 +0300 Subject: [PATCH 063/414] wip(android): Working on new Group Administration settings --- .../android-sdk/src/main/AndroidManifest.xml | 6 + .../controllers/group/GroupAdminActivity.java | 19 +++ .../controllers/group/GroupAdminFragment.java | 126 +++++++++++++++ .../controllers/group/GroupInfoFragment.java | 2 +- .../java/im/actor/sdk/view/BlockView.java | 60 ++++++++ .../main/res/drawable-xhdpi/card_shadow.9.png | Bin 0 -> 99 bytes .../res/drawable-xxhdpi/card_shadow.9.png | Bin 0 -> 14588 bytes .../main/res/layout/fragment_edit_admin.xml | 144 ++++++++++++++++++ .../main/res/layout/fragment_edit_info.xml | 4 +- .../main/res/layout/fragment_group_header.xml | 12 +- .../src/main/res/values/ui_text.xml | 11 +- 11 files changed, 375 insertions(+), 9 deletions(-) create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupAdminActivity.java create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupAdminFragment.java create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/BlockView.java create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xhdpi/card_shadow.9.png create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xxhdpi/card_shadow.9.png create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_edit_admin.xml diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/AndroidManifest.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/AndroidManifest.xml index 8bf892292d..0e80107624 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/AndroidManifest.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/AndroidManifest.xml @@ -151,6 +151,12 @@ android:theme="@style/CommonActivityTheme" android:windowSoftInputMode="adjustNothing|stateAlwaysHidden" /> + + { + startActivity(new Intent(getContext(), GroupTypeActivity.class) + .putExtra(Intents.EXTRA_GROUP_ID, groupVM.getId())); + }); + } + // Group Admins + + // Share History + View shareContainer = res.findViewById(R.id.shareHistoryContainer); + View shareHint = res.findViewById(R.id.shareHistoryHint); + TextView shareHistory = (TextView) res.findViewById(R.id.shareHistory); + shareHistory.setTextColor(style.getTextPrimaryColor()); + TextView shareHistoryValue = (TextView) res.findViewById(R.id.shareHistoryValue); + shareHistoryValue.setTextColor(style.getListActionColor()); + shareHistoryValue.setTypeface(Fonts.medium()); + if (groupVM.getGroupType() == GroupType.GROUP) { + bind(groupVM.getIsHistoryShared(), isShared -> { + if (isShared) { + shareHistoryValue.setVisibility(View.VISIBLE); + shareHistory.setOnClickListener(null); + } else { + shareHistoryValue.setVisibility(View.GONE); + shareHistory.setOnClickListener(v -> { + // TODO: Implement + }); + } + }); + } else { + // Hide for channels + shareContainer.setVisibility(View.GONE); + shareHint.setVisibility(View.GONE); + } + + // Permissions + View permissionsContainer = res.findViewById(R.id.permissionsContainer); + TextView permissions = (TextView) res.findViewById(R.id.permissions); + permissions.setTextColor(style.getTextPrimaryColor()); + if (groupVM.getIsCanEditAdministration().get()) { + permissions.setOnClickListener(v -> { + + }); + } else { + permissionsContainer.setVisibility(View.GONE); + } + + // Group Deletion + TextView delete = (TextView) res.findViewById(R.id.delete); + delete.setTextColor(style.getTextDangerColor()); + if (groupVM.getGroupType() == GroupType.CHANNEL) { + delete.setText(R.string.channel_delete); + } else { + delete.setText(R.string.group_delete); + } + delete.setOnClickListener(v -> { + // TODO: Delete + }); + + return res; + } +} diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java index 68d40148ac..19666236f1 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java @@ -225,7 +225,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa } }); administrationAction.setOnClickListener(view -> { - startActivity(new Intent(getActivity(), GroupTypeActivity.class) + startActivity(new Intent(getActivity(), GroupAdminActivity.class) .putExtra(Intents.EXTRA_GROUP_ID, chatId)); }); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/BlockView.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/BlockView.java new file mode 100644 index 0000000000..0d6c6e5aa1 --- /dev/null +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/BlockView.java @@ -0,0 +1,60 @@ +package im.actor.sdk.view; + +import android.annotation.TargetApi; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.util.AttributeSet; +import android.widget.FrameLayout; +import android.widget.LinearLayout; + +import im.actor.sdk.R; +import im.actor.sdk.util.Screen; + +public class BlockView extends LinearLayout { + + private Drawable topDrawable; + private Drawable bottomDrawable; + + public BlockView(Context context) { + super(context); + init(); + } + + public BlockView(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + public BlockView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public BlockView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + init(); + } + + private void init() { + setPadding(0, Screen.dp(8), 0, Screen.dp(8)); + setWillNotDraw(false); + + bottomDrawable = getResources().getDrawable(R.drawable.card_shadow_bottom); + topDrawable = getResources().getDrawable(R.drawable.card_shadow_top); + } + + @Override + protected void onDraw(Canvas canvas) { + + bottomDrawable.setBounds(0, getHeight() - Screen.dp(8), getWidth(), getHeight() - Screen.dp(4)); + bottomDrawable.draw(canvas); + + topDrawable.setBounds(0, Screen.dp(7), getWidth(), Screen.dp(8)); + topDrawable.draw(canvas); + + super.onDraw(canvas); + } +} diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xhdpi/card_shadow.9.png b/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xhdpi/card_shadow.9.png new file mode 100644 index 0000000000000000000000000000000000000000..cb523d7af4dbcff574218b6fefab04890a0f9193 GIT binary patch literal 99 zcmeAS@N?(olHy`uVBq!ia0vp^EI_Qx!3HEZN38w;q_jL;978H@B_}jA{133<+R*Ud xph3pN$w8-aO_y*-u*-n#R(xh)gJ3dQketqxz z{r#Rl?@xU3tBVWI9!Wi(LI@qH&R3S`_W}CJ9eSAlegEnkx9HblZ+|rgx~--Y=-r|i=hbN(HA|upOTw+9I1?2V zR%(kv+4YH_W%6lLQ50dSkWsYhtg1aDsIsC-azRq$w5;fgtgEunyTnq8Muu-S^rgyN zuN?iaC~k(Kr%O_&)5&zQ8Q0&CltQ5($*QEPX{tyE+fInPX(u?5B-zbVA%W@JUTC|H z5aq?|ZaXZBVpOPq_3o?X^$R&cj~%5bb+IQY8Ce=+?3<&>Wwr`TzOY~Mw zjJx~LZVV=8C{xrkaqOgPPq0Nji7j3V$qX6vUiGq1aOnDV*KL-PRkpZ`P$-wG(n_JODtc~OSCpsatS-wSNunIdQFAT3u{|b- zme;i`%mI|6Ip_|uaER|I3)F%*fzlFn+-NGA0@|+Dr8Z-iBe_iQix9R$HSfrFS zq;bu_6|XhD>I5NnOj50sXhX)fEnUNws%8}>UC60&I+rbwG{y}xjY%Qfm@@MXIX4wU ztks}>PwI+mwxj*hOKsg}YGB`!`n+$`vko_bCK~x20Ahv&w^q|d)fw-6a7jwtIc+-pHgLd(Vsth^{lXa)VjLA6?TtW2hQgl zJ1nWaY9r0S6`j7x!LUpp6i6wrX<5yXMw>?+i*}bZS1LB$!9lbxb3Myw`&i%Vz^Bm> z#G5lDj>b6iYBP{46P z8qApEVj_Y9jtkOY#vB(D5fpG-kOnj6xR{8bfa8KRm@&u2L<9vK7o@?AIW8t5DB!pt z4Q9-7F%dxl#|3FHV~&f72nskZNP`)3Tuekzz;Qtu%$Vb1B7y>r3({c592XN26mVRS z1~cZkn24Z&fPIa-cnMR z&pcZB{^0FH7cTsA_g(Y7j~@Hv#*Ob@I-`Dj<(zo9@UmW}4v!C+l;Oy7euKja|zPX4{b#|fh!P&K!{{taU$W8zN literal 0 HcmV?d00001 diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_edit_admin.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_edit_admin.xml new file mode 100644 index 0000000000..e30a325645 --- /dev/null +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_edit_admin.xml @@ -0,0 +1,144 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_edit_info.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_edit_info.xml index 9610ca22c4..2f8116d260 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_edit_info.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_edit_info.xml @@ -66,9 +66,11 @@ android:gravity="center_vertical" android:hint="@string/group_description_hint" android:inputType="textCapSentences" + android:lines="0" android:minHeight="48dp" android:paddingBottom="8dp" - android:paddingTop="8dp" /> + android:paddingTop="8dp" + android:singleLine="false" /> diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_group_header.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_group_header.xml index c4ad763fc6..d8f203ba2d 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_group_header.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_group_header.xml @@ -165,36 +165,36 @@ Leave channel Administration + Group Type + Channel Type + Public + Private + Delete Group + Delete Channel + + Share History + Shared + Public Group Public groups can be found in search, anyone can join them. Private Group Private Group can only be joined via personal invitation. You are not a member of this group - About the group Set group description Group theme From a93a30ddd2bdeb4fdeecebd673d9d943f0f0dd97 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Tue, 19 Jul 2016 23:24:15 +0300 Subject: [PATCH 064/414] feat(scheme): Adding ShareHistory method --- actor-sdk/sdk-api/actor.json | 30 +++++++++++++++++++ .../models/im/actor/api/scheme.mps | 24 +++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/actor-sdk/sdk-api/actor.json b/actor-sdk/sdk-api/actor.json index 0026329354..89dfe32e9f 100644 --- a/actor-sdk/sdk-api/actor.json +++ b/actor-sdk/sdk-api/actor.json @@ -10763,6 +10763,36 @@ ] } }, + { + "type": "rpc", + "content": { + "name": "ShareHistory", + "header": 2796, + "response": { + "type": "reference", + "name": "Seq" + }, + "doc": [ + "Share History", + { + "type": "reference", + "argument": "groupPeer", + "category": "full", + "description": "Group's peer" + } + ], + "attributes": [ + { + "type": { + "type": "struct", + "childType": "GroupOutPeer" + }, + "id": 1, + "name": "groupPeer" + } + ] + } + }, { "type": "comment", "content": "Invite" diff --git a/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps b/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps index 8099762fb1..0e440a746b 100644 --- a/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps +++ b/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps @@ -9343,6 +9343,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + From c2963ed47f7435b387d9fa7d1859264b7be443cc Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Wed, 20 Jul 2016 00:32:07 +0300 Subject: [PATCH 065/414] feat(android): Implemented administration settings --- .../sdk-core-android/android-app/build.gradle | 2 + .../src/main/java/im/actor/Application.java | 7 +++ .../controllers/group/GroupAdminFragment.java | 42 ++++++++++---- .../controllers/group/GroupInfoFragment.java | 20 +++---- .../controllers/group/GroupTypeFragment.java | 13 ++++- .../main/res/layout/fragment_edit_admin.xml | 57 +++++++++---------- ...inistration.xml => fragment_edit_type.xml} | 5 +- .../src/main/res/values/ui_text.xml | 11 ++++ 8 files changed, 97 insertions(+), 60 deletions(-) rename actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/{fragment_administration.xml => fragment_edit_type.xml} (96%) diff --git a/actor-sdk/sdk-core-android/android-app/build.gradle b/actor-sdk/sdk-core-android/android-app/build.gradle index d754bce2e0..7a49b9cbef 100644 --- a/actor-sdk/sdk-core-android/android-app/build.gradle +++ b/actor-sdk/sdk-core-android/android-app/build.gradle @@ -18,6 +18,7 @@ android { targetSdkVersion 24 versionCode 1 versionName "1.0" + multiDexEnabled true } buildTypes { release { @@ -43,4 +44,5 @@ dependencies { compile project(':actor-sdk:sdk-core-android:android-google-maps') compile 'com.roughike:bottom-bar:1.3.9' + compile 'com.android.support:multidex:1.0.1' } diff --git a/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/Application.java b/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/Application.java index 4166a7ba75..f032f75f34 100644 --- a/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/Application.java +++ b/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/Application.java @@ -1,6 +1,7 @@ package im.actor; import android.os.Bundle; +import android.support.multidex.MultiDex; import android.support.v4.app.Fragment; import android.support.v7.app.ActionBar; import android.view.LayoutInflater; @@ -32,6 +33,12 @@ public class Application extends ActorSDKApplication { + @Override + public void onCreate() { + MultiDex.install(this); + super.onCreate(); + } + @Override public void onConfigureActorSDK() { ActorSDK.sharedActor().setDelegate(new ActorSDKDelegate()); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupAdminFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupAdminFragment.java index b57e0ef0b4..b6d9a57c46 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupAdminFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupAdminFragment.java @@ -13,7 +13,6 @@ import im.actor.sdk.R; import im.actor.sdk.controllers.BaseFragment; import im.actor.sdk.controllers.Intents; -import im.actor.sdk.util.Fonts; import static im.actor.sdk.util.ActorSDKMessenger.messenger; @@ -38,8 +37,12 @@ public GroupAdminFragment() { @Override public void onCreate(Bundle saveInstance) { super.onCreate(saveInstance); - groupVM = messenger().getGroup(getArguments().getInt("groupId")); + if (groupVM.getGroupType() == GroupType.CHANNEL) { + setTitle(R.string.channel_admin_title); + } else { + setTitle(R.string.group_admin_title); + } } @Nullable @@ -69,21 +72,22 @@ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, .putExtra(Intents.EXTRA_GROUP_ID, groupVM.getId())); }); } - // Group Admins // Share History View shareContainer = res.findViewById(R.id.shareHistoryContainer); - View shareHint = res.findViewById(R.id.shareHistoryHint); + TextView shareHint = (TextView) res.findViewById(R.id.shareHistoryHint); + shareHint.setTextColor(style.getTextSecondaryColor()); TextView shareHistory = (TextView) res.findViewById(R.id.shareHistory); shareHistory.setTextColor(style.getTextPrimaryColor()); TextView shareHistoryValue = (TextView) res.findViewById(R.id.shareHistoryValue); shareHistoryValue.setTextColor(style.getListActionColor()); - shareHistoryValue.setTypeface(Fonts.medium()); - if (groupVM.getGroupType() == GroupType.GROUP) { + if (groupVM.getGroupType() == GroupType.GROUP && + groupVM.getIsCanEditAdministration().get()) { bind(groupVM.getIsHistoryShared(), isShared -> { if (isShared) { shareHistoryValue.setVisibility(View.VISIBLE); shareHistory.setOnClickListener(null); + shareHistory.setClickable(false); } else { shareHistoryValue.setVisibility(View.GONE); shareHistory.setOnClickListener(v -> { @@ -98,24 +102,38 @@ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, } // Permissions - View permissionsContainer = res.findViewById(R.id.permissionsContainer); TextView permissions = (TextView) res.findViewById(R.id.permissions); permissions.setTextColor(style.getTextPrimaryColor()); - if (groupVM.getIsCanEditAdministration().get()) { - permissions.setOnClickListener(v -> { - - }); + TextView permissionsHint = (TextView) res.findViewById(R.id.permissionsHint); + permissionsHint.setTextColor(style.getTextSecondaryColor()); + View permissionsDiv = res.findViewById(R.id.permissionsDiv); + if (groupVM.getGroupType() == GroupType.CHANNEL) { + permissionsHint.setText(R.string.channel_permissions_hint); } else { - permissionsContainer.setVisibility(View.GONE); + permissionsHint.setText(R.string.group_permissions_hint); } +// if (groupVM.getIsCanEditAdministration().get()) { +// permissions.setOnClickListener(v -> { +// +// }); +// } else { +// permissions.setVisibility(View.GONE); +// permissionsDiv.setVisibility(View.GONE); +// } + permissions.setVisibility(View.GONE); + permissionsDiv.setVisibility(View.GONE); // Group Deletion TextView delete = (TextView) res.findViewById(R.id.delete); delete.setTextColor(style.getTextDangerColor()); + TextView deleteHint = (TextView) res.findViewById(R.id.deleteHint); + deleteHint.setTextColor(style.getTextSecondaryColor()); if (groupVM.getGroupType() == GroupType.CHANNEL) { delete.setText(R.string.channel_delete); + deleteHint.setText(R.string.channel_delete_hint); } else { delete.setText(R.string.group_delete); + deleteHint.setText(R.string.group_delete_hint); } delete.setOnClickListener(v -> { // TODO: Delete diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java index 19666236f1..e83c985698 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java @@ -217,17 +217,15 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa }); // Administration - bind(groupVM.getIsCanEditAdministration(), groupVM.getIsCanEditShortName(), (canEditAdministration, valueModel, canEditShortName, valueModel2) -> { - if (canEditAdministration || canEditShortName) { - administrationAction.setVisibility(View.VISIBLE); - } else { - administrationAction.setVisibility(View.GONE); - } - }); - administrationAction.setOnClickListener(view -> { - startActivity(new Intent(getActivity(), GroupAdminActivity.class) - .putExtra(Intents.EXTRA_GROUP_ID, chatId)); - }); + if (groupVM.getIsCanEditAdministration().get() || + groupVM.getIsCanEditShortName().get()) { + administrationAction.setOnClickListener(view -> { + startActivity(new Intent(getActivity(), GroupAdminActivity.class) + .putExtra(Intents.EXTRA_GROUP_ID, chatId)); + }); + } else { + administrationAction.setVisibility(View.GONE); + } // Async Members // Showing member only when members available and async members is enabled diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupTypeFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupTypeFragment.java index 8f920d2f17..76f269c6df 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupTypeFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupTypeFragment.java @@ -14,6 +14,7 @@ import android.widget.Toast; import im.actor.core.viewmodel.GroupVM; +import im.actor.sdk.ActorSDK; import im.actor.sdk.R; import im.actor.sdk.controllers.BaseFragment; import im.actor.sdk.util.Fonts; @@ -51,7 +52,7 @@ public void onCreate(Bundle saveInstance) { @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - View res = inflater.inflate(R.layout.fragment_administration, container, false); + View res = inflater.inflate(R.layout.fragment_edit_type, container, false); res.setBackgroundColor(style.getBackyardBackgroundColor()); TextView publicTitle = (TextView) res.findViewById(R.id.publicTitle); publicTitle.setTextColor(style.getTextPrimaryColor()); @@ -63,9 +64,15 @@ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, privateDescription.setTextColor(style.getTextSecondaryColor()); TextView publicLinkPrefix = (TextView) res.findViewById(R.id.publicLinkPrefix); publicLinkPrefix.setTextColor(style.getTextSecondaryColor()); - publicLinkPrefix.setTypeface(Fonts.medium()); + String prefix = ActorSDK.sharedActor().getGroupInvitePrefix(); + if (prefix == null) { + prefix = "@"; + } + publicLinkPrefix.setText(prefix); RadioButton publicRadio = (RadioButton) res.findViewById(R.id.publicRadio); RadioButton privateRadio = (RadioButton) res.findViewById(R.id.privateRadio); + View publicSelector = res.findViewById(R.id.publicSelector); + View privateSelector = res.findViewById(R.id.privateSelector); publicShortName = (EditText) res.findViewById(R.id.publicLink); View publicLinkContainer = res.findViewById(R.id.publicContainer); View publicShadowTop = res.findViewById(R.id.shadowTop); @@ -111,7 +118,9 @@ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, } }; publicRadio.setOnClickListener(publicClick); + publicSelector.setOnClickListener(publicClick); privateRadio.setOnClickListener(privateClick); + privateSelector.setOnClickListener(privateClick); return res; } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_edit_admin.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_edit_admin.xml index e30a325645..7990ea45f3 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_edit_admin.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_edit_admin.xml @@ -15,6 +15,7 @@ android:id="@+id/generalBlock" android:layout_width="match_parent" android:layout_height="wrap_content" + android:layout_marginTop="8dp" android:background="@drawable/card_shadow" android:orientation="vertical"> @@ -45,27 +46,31 @@ - - - - - + android:paddingRight="16dp" + android:text="@string/group_permissions" + android:textSize="16sp" /> + + - - - - + android:layout_marginBottom="8dp" + android:paddingLeft="16dp" + android:paddingRight="16dp" + android:textSize="14sp" /> \ No newline at end of file diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_administration.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_edit_type.xml similarity index 96% rename from actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_administration.xml rename to actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_edit_type.xml index aa83f0b0cf..e13fcd6d31 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_administration.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_edit_type.xml @@ -44,7 +44,6 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="4dp" - android:background="@android:color/white" android:gravity="center_vertical" android:text="@string/group_public_group_title" android:textSize="16sp" /> @@ -87,7 +86,6 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="4dp" - android:background="@android:color/white" android:gravity="center_vertical" android:text="@string/group_private_group_title" android:textSize="16sp" /> @@ -96,7 +94,6 @@ android:id="@+id/privateDescription" android:layout_width="match_parent" android:layout_height="wrap_content" - android:background="@android:color/white" android:text="@string/group_private_group_text" android:textSize="14sp" /> @@ -129,7 +126,7 @@ android:layout_width="wrap_content" android:layout_height="match_parent" android:gravity="center_vertical" - android:paddingLeft="48dp" + android:paddingLeft="16dp" android:paddingRight="1dp" android:text="\@" android:textSize="18sp" /> diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml index cd689b4637..07cd199118 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml @@ -361,21 +361,32 @@ Leave channel Administration + Group Administration + Channel Administration + + Permissions + Control what is possible in this group + Control what is possible in this channel + Group Type Channel Type Public Private Delete Group + You will lose all messages in this group. Delete Channel + You will lose all messages in this channel. Share History Shared + All members will see all messages Public Group Public groups can be found in search, anyone can join them. Private Group Private Group can only be joined via personal invitation. + You are not a member of this group About the group Set group description From bffa97c68a41a499999c630d15a0d67901f94b62 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Wed, 20 Jul 2016 01:32:02 +0300 Subject: [PATCH 066/414] feat(android+core): Implemented permissions editing --- .../android-sdk/src/main/AndroidManifest.xml | 6 + .../controllers/group/GroupAdminFragment.java | 19 ++- .../group/GroupPermissionsActivity.java | 19 +++ .../group/GroupPermissionsFragment.java | 119 ++++++++++++++++++ .../res/layout/fragment_edit_permissions.xml | 63 ++++++++++ .../src/main/res/values/ui_text.xml | 3 + .../main/java/im/actor/core/Messenger.java | 26 ++++ .../actor/core/entity/GroupPermissions.java | 36 ++++++ .../core/modules/groups/GroupsModule.java | 18 +++ 9 files changed, 299 insertions(+), 10 deletions(-) create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupPermissionsActivity.java create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupPermissionsFragment.java create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_edit_permissions.xml create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/GroupPermissions.java diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/AndroidManifest.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/AndroidManifest.xml index 0e80107624..4f38c4ff30 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/AndroidManifest.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/AndroidManifest.xml @@ -163,6 +163,12 @@ android:theme="@style/CommonActivityTheme" android:windowSoftInputMode="adjustNothing|stateAlwaysHidden" /> + + { -// -// }); -// } else { -// permissions.setVisibility(View.GONE); -// permissionsDiv.setVisibility(View.GONE); -// } - permissions.setVisibility(View.GONE); - permissionsDiv.setVisibility(View.GONE); + if (groupVM.getIsCanEditAdministration().get() && groupVM.getGroupType() == GroupType.GROUP) { + permissions.setOnClickListener(v -> { + startActivity(new Intent(getContext(), GroupPermissionsActivity.class) + .putExtra(Intents.EXTRA_GROUP_ID, groupVM.getId())); + }); + } else { + permissions.setVisibility(View.GONE); + permissionsDiv.setVisibility(View.GONE); + } // Group Deletion TextView delete = (TextView) res.findViewById(R.id.delete); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupPermissionsActivity.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupPermissionsActivity.java new file mode 100644 index 0000000000..cb2a61b5a1 --- /dev/null +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupPermissionsActivity.java @@ -0,0 +1,19 @@ +package im.actor.sdk.controllers.group; + +import android.os.Bundle; + +import im.actor.sdk.controllers.Intents; +import im.actor.sdk.controllers.activity.BaseFragmentActivity; + +public class GroupPermissionsActivity extends BaseFragmentActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (savedInstanceState == null) { + showFragment(GroupPermissionsFragment.create( + getIntent().getIntExtra(Intents.EXTRA_GROUP_ID, 0)), false); + } + } +} diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupPermissionsFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupPermissionsFragment.java new file mode 100644 index 0000000000..25a23ad0fa --- /dev/null +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupPermissionsFragment.java @@ -0,0 +1,119 @@ +package im.actor.sdk.controllers.group; + +import android.app.Activity; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CheckBox; + +import fr.castorflex.android.circularprogressbar.CircularProgressBar; +import im.actor.core.entity.GroupPermissions; +import im.actor.core.entity.GroupType; +import im.actor.sdk.R; +import im.actor.sdk.controllers.BaseFragment; + +import static im.actor.sdk.util.ActorSDKMessenger.messenger; + +public class GroupPermissionsFragment extends BaseFragment { + + public static GroupPermissionsFragment create(int chatId) { + Bundle args = new Bundle(); + args.putInt("groupId", chatId); + GroupPermissionsFragment res = new GroupPermissionsFragment(); + res.setArguments(args); + return res; + } + + private GroupPermissions permissions; + private int groupId; + + private CircularProgressBar progress; + private View scrollContainer; + private CheckBox canEditInfo; + + public GroupPermissionsFragment() { + setRootFragment(true); + setHomeAsUp(true); + setShowHome(true); + } + + @Override + public void onCreate(Bundle saveInstance) { + super.onCreate(saveInstance); + groupId = getArguments().getInt("groupId"); + if (messenger().getGroup(groupId).getGroupType() == GroupType.CHANNEL) { + setTitle(R.string.channel_admin_title); + } else { + setTitle(R.string.group_admin_title); + } + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View res = inflater.inflate(R.layout.fragment_edit_permissions, container, false); + View rootContainer = res.findViewById(R.id.rootContainer); + rootContainer.setBackgroundColor(style.getBackyardBackgroundColor()); + canEditInfo = (CheckBox) res.findViewById(R.id.canEditValue); + scrollContainer = res.findViewById(R.id.scrollContainer); + progress = (CircularProgressBar) res.findViewById(R.id.progress); + progress.setIndeterminate(true); + return res; + } + + @Override + public void onResume() { + super.onResume(); + if (permissions == null) { + progress.setVisibility(View.VISIBLE); + scrollContainer.setVisibility(View.GONE); + wrap(messenger().loadGroupPermissions(groupId)).then(r -> { + goneView(progress); + showView(scrollContainer); + this.permissions = r; + bindView(); + }); + } else { + progress.setVisibility(View.GONE); + scrollContainer.setVisibility(View.VISIBLE); + } + } + + public void bindView() { + Activity activity = getActivity(); + if (activity != null) { + activity.invalidateOptionsMenu(); + } + canEditInfo.setChecked(permissions.isMembersCanEditInfo()); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + super.onCreateOptionsMenu(menu, inflater); + if (permissions != null) { + inflater.inflate(R.menu.next, menu); + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == R.id.next) { + if (permissions.isMembersCanEditInfo() != canEditInfo.isChecked()) { + permissions.setMembersCanEditInfo(canEditInfo.isChecked()); + execute(messenger().saveGroupPermissions(groupId, permissions).then(r -> { + finishActivity(); + })); + } else { + finishActivity(); + } + return true; + } else { + return super.onOptionsItemSelected(item); + } + } +} \ No newline at end of file diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_edit_permissions.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_edit_permissions.xml new file mode 100644 index 0000000000..07cb8a6378 --- /dev/null +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_edit_permissions.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml index 07cd199118..7290f30972 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml @@ -381,6 +381,9 @@ Shared All members will see all messages + All members can edit group info + All members can edit group info + Public Group Public groups can be found in search, anyone can join them. Private Group diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java index 928a183ad9..6e47cd2f4e 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java @@ -22,6 +22,7 @@ import im.actor.core.entity.FileReference; import im.actor.core.entity.Group; import im.actor.core.entity.GroupMembersSlice; +import im.actor.core.entity.GroupPermissions; import im.actor.core.entity.MentionFilterResult; import im.actor.core.entity.MessageSearchEntity; import im.actor.core.entity.Peer; @@ -1436,6 +1437,31 @@ public Promise editGroupShortName(int gid, String shortName) { return modules.getGroupsModule().editShortName(gid, shortName); } + /** + * Load Group's permissions + * + * @param gid group's id + * @return promise of permissions + */ + @NotNull + @ObjectiveCName("loadGroupPermissionsWithGid:") + public Promise loadGroupPermissions(int gid) { + return modules.getGroupsModule().loadAdminSettings(gid); + } + + /** + * Save Group's permissions + * + * @param gid group's id + * @param adminSettings settings + * @return promise of void + */ + @NotNull + @ObjectiveCName("saveGroupPermissionsWithGid:withSettings:") + public Promise saveGroupPermissions(int gid, GroupPermissions adminSettings) { + return modules.getGroupsModule().saveAdminSettings(gid, adminSettings); + } + /** * Change group avatar * diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/GroupPermissions.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/GroupPermissions.java new file mode 100644 index 0000000000..9c11d4ca6c --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/GroupPermissions.java @@ -0,0 +1,36 @@ +package im.actor.core.entity; + +import com.google.j2objc.annotations.ObjectiveCName; + +import im.actor.core.api.ApiAdminSettings; +import im.actor.runtime.collections.SparseArray; + +public class GroupPermissions { + + private ApiAdminSettings settings; + + public GroupPermissions(ApiAdminSettings settings) { + this.settings = settings; + } + + @ObjectiveCName("isMembersCanEditInfo") + public boolean isMembersCanEditInfo() { + return settings.canMembersEditGroupInfo(); + } + + @ObjectiveCName("setMembersCanEditInfo:") + public void setMembersCanEditInfo(boolean canEditInfo) { + SparseArray unmapped = settings.getUnmappedObjects(); + settings = new ApiAdminSettings( + settings.showAdminsToMembers(), + settings.canMembersInvite(), + canEditInfo, + settings.canAdminsEditGroupInfo() + ); + settings.setUnmappedObjects(unmapped); + } + + public ApiAdminSettings getApiSettings() { + return settings; + } +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsModule.java index 57627ed821..ac3b047ac0 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsModule.java @@ -8,6 +8,7 @@ import java.util.HashMap; import java.util.List; +import im.actor.core.api.ApiAdminSettings; import im.actor.core.api.ApiGroupOutPeer; import im.actor.core.api.ApiGroupType; import im.actor.core.api.ApiOutPeer; @@ -24,15 +25,18 @@ import im.actor.core.api.rpc.RequestJoinGroup; import im.actor.core.api.rpc.RequestKickUser; import im.actor.core.api.rpc.RequestLeaveGroup; +import im.actor.core.api.rpc.RequestLoadAdminSettings; import im.actor.core.api.rpc.RequestLoadMembers; import im.actor.core.api.rpc.RequestMakeUserAdminObsolete; import im.actor.core.api.rpc.RequestRevokeIntegrationToken; import im.actor.core.api.rpc.RequestRevokeInviteUrl; +import im.actor.core.api.rpc.RequestSaveAdminSettings; import im.actor.core.api.rpc.RequestTransferOwnership; import im.actor.core.api.rpc.ResponseIntegrationToken; import im.actor.core.api.rpc.ResponseInviteUrl; import im.actor.core.entity.Group; import im.actor.core.entity.GroupMembersSlice; +import im.actor.core.entity.GroupPermissions; import im.actor.core.entity.Peer; import im.actor.core.entity.PeerType; import im.actor.core.entity.User; @@ -239,6 +243,20 @@ public Promise editShortName(final int gid, final String shortName) { .flatMap(r -> updates().waitForUpdate(r.getSeq())); } + public Promise loadAdminSettings(int gid) { + return getGroups().getValueAsync(gid) + .flatMap(group -> api(new RequestLoadAdminSettings(new ApiGroupOutPeer(group.getGroupId(), group.getAccessHash())))) + .map(r -> new GroupPermissions(r.getSettings())); + } + + public Promise saveAdminSettings(int gid, GroupPermissions adminSettings) { + return getGroups().getValueAsync(gid) + .flatMap(group -> api(new RequestSaveAdminSettings( + new ApiGroupOutPeer(group.getGroupId(), group.getAccessHash()), + adminSettings.getApiSettings()))) + .map(r -> null); + } + public Promise loadMembers(int gid, int limit, byte[] next) { return getGroups().getValueAsync(gid) .flatMap(group -> From 12d276decff7ac7867961548b20240d9658d310e Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Wed, 20 Jul 2016 15:48:08 +0300 Subject: [PATCH 067/414] feat(core+android): Can Leave, Can Delete and CanInviteViaLink support --- .../controllers/group/AddMemberFragment.java | 28 ++- .../controllers/group/GroupAdminFragment.java | 21 +- .../controllers/group/GroupInfoFragment.java | 28 ++- .../main/java/im/actor/core/Messenger.java | 24 +++ .../java/im/actor/core/api/ApiGroupFull.java | 37 +++- .../im/actor/core/api/ApiKeyGroupHolder.java | 64 ++++++ .../java/im/actor/core/api/ApiKeyGroupId.java | 1 + .../im/actor/core/api/parser/RpcParser.java | 2 + .../actor/core/api/parser/UpdatesParser.java | 3 + .../core/api/rpc/RequestDeleteGroup.java | 65 ++++++ .../core/api/rpc/RequestShareHistory.java | 65 ++++++ .../api/rpc/ResponseSendEncryptedPackage.java | 48 ++--- .../updates/UpdateGroupCanDeleteChanged.java | 70 ++++++ .../updates/UpdateGroupCanInviteViaLink.java | 70 ++++++ .../updates/UpdateGroupCanLeaveChanged.java | 70 ++++++ .../main/java/im/actor/core/entity/Group.java | 199 ++++++++++++++++-- .../core/modules/groups/GroupsModule.java | 16 ++ .../core/modules/groups/GroupsProcessor.java | 8 +- .../modules/groups/router/GroupRouter.java | 27 +++ .../java/im/actor/core/viewmodel/GroupVM.java | 49 ++++- 20 files changed, 818 insertions(+), 77 deletions(-) create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiKeyGroupHolder.java create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/RequestDeleteGroup.java create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/RequestShareHistory.java create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanDeleteChanged.java create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanInviteViaLink.java create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanLeaveChanged.java diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/AddMemberFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/AddMemberFragment.java index 232b138d9c..6ce85f7b25 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/AddMemberFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/AddMemberFragment.java @@ -30,6 +30,8 @@ public static AddMemberFragment create(int gid) { return res; } + private GroupVM groupVM; + public AddMemberFragment() { super(true, true, false); setRootFragment(true); @@ -37,22 +39,30 @@ public AddMemberFragment() { setHomeAsUp(true); } + @Override + public void onCreate(Bundle saveInstance) { + super.onCreate(saveInstance); + + groupVM = messenger().getGroup(getArguments().getInt("GROUP_ID")); + } + @Override protected void addFootersAndHeaders() { - addFooterOrHeaderAction(ActorSDK.sharedActor().style.getActionAddContactColor(), R.drawable.ic_person_add_white_24dp, R.string.contacts_invite_via_link, false, new Runnable() { - @Override - public void run() { - startActivity(Intents.inviteLink(getArguments().getInt("GROUP_ID", 0), getActivity())); - } - }, true); + + if (groupVM.getIsCanInviteViaLink().get()) { + addFooterOrHeaderAction(ActorSDK.sharedActor().style.getActionAddContactColor(), R.drawable.ic_person_add_white_24dp, R.string.contacts_invite_via_link, false, new Runnable() { + @Override + public void run() { + startActivity(Intents.inviteLink(getArguments().getInt("GROUP_ID", 0), getActivity())); + } + }, true); + } } @Override public void onItemClicked(Contact contact) { - final int gid = getArguments().getInt("GROUP_ID"); final UserVM userModel = users().get(contact.getUid()); - final GroupVM groupVM = groups().get(gid); for (GroupMember uid : groupVM.getMembers().get()) { if (uid.getUid() == userModel.getId()) { @@ -66,7 +76,7 @@ public void onItemClicked(Contact contact) { .setPositiveButton(R.string.alert_group_add_yes, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog2, int which) { - execute(messenger().inviteMember(gid, userModel.getId()), + execute(messenger().inviteMember(groupVM.getId(), userModel.getId()), R.string.progress_common, new CommandCallback() { @Override public void onResult(Void res) { diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupAdminFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupAdminFragment.java index b41338e534..2e3b26ddd6 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupAdminFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupAdminFragment.java @@ -10,6 +10,7 @@ import im.actor.core.entity.GroupType; import im.actor.core.viewmodel.GroupVM; +import im.actor.sdk.ActorSDK; import im.actor.sdk.R; import im.actor.sdk.controllers.BaseFragment; import im.actor.sdk.controllers.Intents; @@ -91,7 +92,7 @@ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, } else { shareHistoryValue.setVisibility(View.GONE); shareHistory.setOnClickListener(v -> { - // TODO: Implement + execute(messenger().shareHistory(groupVM.getId())); }); } }); @@ -123,6 +124,7 @@ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, } // Group Deletion + View deleteContainer = res.findViewById(R.id.deleteContainer); TextView delete = (TextView) res.findViewById(R.id.delete); delete.setTextColor(style.getTextDangerColor()); TextView deleteHint = (TextView) res.findViewById(R.id.deleteHint); @@ -134,8 +136,21 @@ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, delete.setText(R.string.group_delete); deleteHint.setText(R.string.group_delete_hint); } - delete.setOnClickListener(v -> { - // TODO: Delete + bind(groupVM.getIsCanDelete(), canDelete -> { + if (canDelete) { + deleteContainer.setVisibility(View.VISIBLE); + delete.setOnClickListener(v -> { + execute(messenger().deleteGroup(groupVM.getId())).then(r -> { + ActorSDK.sharedActor().startMessagingApp(getActivity()); + finishActivity(); + }); + }); + } else { + deleteContainer.setVisibility(View.GONE); + delete.setOnClickListener(null); + delete.setClickable(false); + } + }); return res; diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java index e83c985698..e926dbeffa 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java @@ -248,18 +248,26 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa }); // Leave - leaveAction.setOnClickListener(view1 -> { - new AlertDialog.Builder(getActivity()) - .setMessage(getString(R.string.alert_leave_group_message).replace("%1$s", - groupVM.getName().get())) - .setPositiveButton(R.string.alert_leave_group_yes, (dialog2, which) -> { - execute(messenger().leaveGroup(chatId)); - }) - .setNegativeButton(R.string.dialog_cancel, null) - .show() - .setCanceledOnTouchOutside(true); + bind(groupVM.getIsCanLeave(), canLeave -> { + if (canLeave) { + leaveAction.setVisibility(View.VISIBLE); + leaveAction.setOnClickListener(view1 -> { + new AlertDialog.Builder(getActivity()) + .setMessage(getString(R.string.alert_leave_group_message).replace("%1$s", + groupVM.getName().get())) + .setPositiveButton(R.string.alert_leave_group_yes, (dialog2, which) -> { + execute(messenger().leaveGroup(chatId)); + }) + .setNegativeButton(R.string.dialog_cancel, null) + .show() + .setCanceledOnTouchOutside(true); + }); + } else { + leaveAction.setVisibility(View.GONE); + } }); + listView.addHeaderView(header, null, false); // diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java index 6e47cd2f4e..f84c9163c9 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java @@ -1529,6 +1529,30 @@ public Command leaveGroup(final int gid) { .failure(e -> callback.onError(e)); } + /** + * Delete Group + * + * @param gid group's id + * @return Promise of void + */ + @NotNull + @ObjectiveCName("deleteGroupWithGid:") + public Promise deleteGroup(int gid) { + return modules.getGroupsModule().deleteGroup(gid); + } + + /** + * Share Group History + * + * @param gid group's id + * @return Promise of void + */ + @NotNull + @ObjectiveCName("shareHistoryWithGid:") + public Promise shareHistory(int gid) { + return modules.getGroupsModule().shareHistory(gid); + } + /** * Adding member to group * diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiGroupFull.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiGroupFull.java index 72ac66feb2..c09f0f4ff5 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiGroupFull.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiGroupFull.java @@ -33,8 +33,11 @@ public class ApiGroupFull extends BserObject { private Boolean canEditAdminList; private Boolean canViewAdminList; private Boolean canEditAdminSettings; + private Boolean canInviteViaLink; + private Boolean canDelete; + private Boolean canLeave; - public ApiGroupFull(int id, long createDate, @Nullable Integer ownerUid, @NotNull List members, @Nullable String theme, @Nullable String about, @Nullable ApiMapValue ext, @Nullable Boolean isAsyncMembers, @Nullable Boolean canViewMembers, @Nullable Boolean canInvitePeople, @Nullable Boolean isSharedHistory, @Nullable Boolean canEditGroupInfo, @Nullable String shortName, @Nullable Boolean canEditShortName, @Nullable Boolean canEditAdminList, @Nullable Boolean canViewAdminList, @Nullable Boolean canEditAdminSettings) { + public ApiGroupFull(int id, long createDate, @Nullable Integer ownerUid, @NotNull List members, @Nullable String theme, @Nullable String about, @Nullable ApiMapValue ext, @Nullable Boolean isAsyncMembers, @Nullable Boolean canViewMembers, @Nullable Boolean canInvitePeople, @Nullable Boolean isSharedHistory, @Nullable Boolean canEditGroupInfo, @Nullable String shortName, @Nullable Boolean canEditShortName, @Nullable Boolean canEditAdminList, @Nullable Boolean canViewAdminList, @Nullable Boolean canEditAdminSettings, @Nullable Boolean canInviteViaLink, @Nullable Boolean canDelete, @Nullable Boolean canLeave) { this.id = id; this.createDate = createDate; this.ownerUid = ownerUid; @@ -52,6 +55,9 @@ public ApiGroupFull(int id, long createDate, @Nullable Integer ownerUid, @NotNul this.canEditAdminList = canEditAdminList; this.canViewAdminList = canViewAdminList; this.canEditAdminSettings = canEditAdminSettings; + this.canInviteViaLink = canInviteViaLink; + this.canDelete = canDelete; + this.canLeave = canLeave; } public ApiGroupFull() { @@ -141,6 +147,21 @@ public Boolean canEditAdminSettings() { return this.canEditAdminSettings; } + @Nullable + public Boolean canInviteViaLink() { + return this.canInviteViaLink; + } + + @Nullable + public Boolean canDelete() { + return this.canDelete; + } + + @Nullable + public Boolean canLeave() { + return this.canLeave; + } + @Override public void parse(BserValues values) throws IOException { this.id = values.getInt(1); @@ -164,6 +185,9 @@ public void parse(BserValues values) throws IOException { this.canEditAdminList = values.optBool(16); this.canViewAdminList = values.optBool(17); this.canEditAdminSettings = values.optBool(18); + this.canInviteViaLink = values.optBool(19); + this.canDelete = values.optBool(20); + this.canLeave = values.optBool(21); if (values.hasRemaining()) { setUnmappedObjects(values.buildRemaining()); } @@ -216,6 +240,15 @@ public void serialize(BserWriter writer) throws IOException { if (this.canEditAdminSettings != null) { writer.writeBool(18, this.canEditAdminSettings); } + if (this.canInviteViaLink != null) { + writer.writeBool(19, this.canInviteViaLink); + } + if (this.canDelete != null) { + writer.writeBool(20, this.canDelete); + } + if (this.canLeave != null) { + writer.writeBool(21, this.canLeave); + } if (this.getUnmappedObjects() != null) { SparseArray unmapped = this.getUnmappedObjects(); for (int i = 0; i < unmapped.size(); i++) { @@ -244,6 +277,8 @@ public String toString() { res += ", canEditAdminList=" + this.canEditAdminList; res += ", canViewAdminList=" + this.canViewAdminList; res += ", canEditAdminSettings=" + this.canEditAdminSettings; + res += ", canInviteViaLink=" + this.canInviteViaLink; + res += ", canDelete=" + this.canDelete; res += "}"; return res; } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiKeyGroupHolder.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiKeyGroupHolder.java new file mode 100644 index 0000000000..e21577a32c --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiKeyGroupHolder.java @@ -0,0 +1,64 @@ +package im.actor.core.api; +/* + * Generated by the Actor API Scheme generator. DO NOT EDIT! + */ + +import im.actor.runtime.bser.*; +import im.actor.runtime.collections.*; +import static im.actor.runtime.bser.Utils.*; +import im.actor.core.network.parser.*; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.NotNull; +import com.google.j2objc.annotations.ObjectiveCName; +import java.io.IOException; +import java.util.List; +import java.util.ArrayList; + +public class ApiKeyGroupHolder extends BserObject { + + private int uid; + private ApiEncryptionKeyGroup keyGroup; + + public ApiKeyGroupHolder(int uid, @NotNull ApiEncryptionKeyGroup keyGroup) { + this.uid = uid; + this.keyGroup = keyGroup; + } + + public ApiKeyGroupHolder() { + + } + + public int getUid() { + return this.uid; + } + + @NotNull + public ApiEncryptionKeyGroup getKeyGroup() { + return this.keyGroup; + } + + @Override + public void parse(BserValues values) throws IOException { + this.uid = values.getInt(1); + this.keyGroup = values.getObj(2, new ApiEncryptionKeyGroup()); + } + + @Override + public void serialize(BserWriter writer) throws IOException { + writer.writeInt(1, this.uid); + if (this.keyGroup == null) { + throw new IOException(); + } + writer.writeObject(2, this.keyGroup); + } + + @Override + public String toString() { + String res = "struct KeyGroupHolder{"; + res += "uid=" + this.uid; + res += ", keyGroup=" + this.keyGroup; + res += "}"; + return res; + } + +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiKeyGroupId.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiKeyGroupId.java index 05d1d404a5..44b13d3f3b 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiKeyGroupId.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiKeyGroupId.java @@ -52,6 +52,7 @@ public void serialize(BserWriter writer) throws IOException { public String toString() { String res = "struct KeyGroupId{"; res += "uid=" + this.uid; + res += ", keyGroupId=" + this.keyGroupId; res += "}"; return res; } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/parser/RpcParser.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/parser/RpcParser.java index dce6cf7ff6..514263a785 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/parser/RpcParser.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/parser/RpcParser.java @@ -93,6 +93,8 @@ public RpcScope read(int type, byte[] payload) throws IOException { case 2789: return RequestTransferOwnership.fromBytes(payload); case 2790: return RequestLoadAdminSettings.fromBytes(payload); case 2792: return RequestSaveAdminSettings.fromBytes(payload); + case 2795: return RequestDeleteGroup.fromBytes(payload); + case 2796: return RequestShareHistory.fromBytes(payload); case 177: return RequestGetGroupInviteUrl.fromBytes(payload); case 179: return RequestRevokeInviteUrl.fromBytes(payload); case 180: return RequestJoinGroup.fromBytes(payload); diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/parser/UpdatesParser.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/parser/UpdatesParser.java index 600d42a4b5..a372a3cf8a 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/parser/UpdatesParser.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/parser/UpdatesParser.java @@ -65,6 +65,9 @@ public Update read(int type, byte[] payload) throws IOException { case 2632: return UpdateGroupCanEditUsernameChanged.fromBytes(payload); case 2633: return UpdateGroupCanEditAdminsChanged.fromBytes(payload); case 2640: return UpdateGroupCanViewAdminsChanged.fromBytes(payload); + case 2646: return UpdateGroupCanInviteViaLink.fromBytes(payload); + case 2647: return UpdateGroupCanLeaveChanged.fromBytes(payload); + case 2648: return UpdateGroupCanDeleteChanged.fromBytes(payload); case 2641: return UpdateGroupCanEditAdminSettingsChanged.fromBytes(payload); case 2612: return UpdateGroupMemberChanged.fromBytes(payload); case 2615: return UpdateGroupMembersBecameAsync.fromBytes(payload); diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/RequestDeleteGroup.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/RequestDeleteGroup.java new file mode 100644 index 0000000000..2e1be595fd --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/RequestDeleteGroup.java @@ -0,0 +1,65 @@ +package im.actor.core.api.rpc; +/* + * Generated by the Actor API Scheme generator. DO NOT EDIT! + */ + +import im.actor.runtime.bser.*; +import im.actor.runtime.collections.*; +import static im.actor.runtime.bser.Utils.*; +import im.actor.core.network.parser.*; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.NotNull; +import com.google.j2objc.annotations.ObjectiveCName; +import java.io.IOException; +import java.util.List; +import java.util.ArrayList; +import im.actor.core.api.*; + +public class RequestDeleteGroup extends Request { + + public static final int HEADER = 0xaeb; + public static RequestDeleteGroup fromBytes(byte[] data) throws IOException { + return Bser.parse(new RequestDeleteGroup(), data); + } + + private ApiGroupOutPeer groupPeer; + + public RequestDeleteGroup(@NotNull ApiGroupOutPeer groupPeer) { + this.groupPeer = groupPeer; + } + + public RequestDeleteGroup() { + + } + + @NotNull + public ApiGroupOutPeer getGroupPeer() { + return this.groupPeer; + } + + @Override + public void parse(BserValues values) throws IOException { + this.groupPeer = values.getObj(1, new ApiGroupOutPeer()); + } + + @Override + public void serialize(BserWriter writer) throws IOException { + if (this.groupPeer == null) { + throw new IOException(); + } + writer.writeObject(1, this.groupPeer); + } + + @Override + public String toString() { + String res = "rpc DeleteGroup{"; + res += "groupPeer=" + this.groupPeer; + res += "}"; + return res; + } + + @Override + public int getHeaderKey() { + return HEADER; + } +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/RequestShareHistory.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/RequestShareHistory.java new file mode 100644 index 0000000000..1ea3fc3fa7 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/RequestShareHistory.java @@ -0,0 +1,65 @@ +package im.actor.core.api.rpc; +/* + * Generated by the Actor API Scheme generator. DO NOT EDIT! + */ + +import im.actor.runtime.bser.*; +import im.actor.runtime.collections.*; +import static im.actor.runtime.bser.Utils.*; +import im.actor.core.network.parser.*; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.NotNull; +import com.google.j2objc.annotations.ObjectiveCName; +import java.io.IOException; +import java.util.List; +import java.util.ArrayList; +import im.actor.core.api.*; + +public class RequestShareHistory extends Request { + + public static final int HEADER = 0xaec; + public static RequestShareHistory fromBytes(byte[] data) throws IOException { + return Bser.parse(new RequestShareHistory(), data); + } + + private ApiGroupOutPeer groupPeer; + + public RequestShareHistory(@NotNull ApiGroupOutPeer groupPeer) { + this.groupPeer = groupPeer; + } + + public RequestShareHistory() { + + } + + @NotNull + public ApiGroupOutPeer getGroupPeer() { + return this.groupPeer; + } + + @Override + public void parse(BserValues values) throws IOException { + this.groupPeer = values.getObj(1, new ApiGroupOutPeer()); + } + + @Override + public void serialize(BserWriter writer) throws IOException { + if (this.groupPeer == null) { + throw new IOException(); + } + writer.writeObject(1, this.groupPeer); + } + + @Override + public String toString() { + String res = "rpc ShareHistory{"; + res += "groupPeer=" + this.groupPeer; + res += "}"; + return res; + } + + @Override + public int getHeaderKey() { + return HEADER; + } +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/ResponseSendEncryptedPackage.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/ResponseSendEncryptedPackage.java index 2aabe091db..a80d449c85 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/ResponseSendEncryptedPackage.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/ResponseSendEncryptedPackage.java @@ -22,15 +22,11 @@ public static ResponseSendEncryptedPackage fromBytes(byte[] data) throws IOExcep return Bser.parse(new ResponseSendEncryptedPackage(), data); } - private Integer seq; - private byte[] state; private Long date; private List obsoleteKeyGroups; - private List missedKeyGroups; + private List missedKeyGroups; - public ResponseSendEncryptedPackage(@Nullable Integer seq, @Nullable byte[] state, @Nullable Long date, @NotNull List obsoleteKeyGroups, @NotNull List missedKeyGroups) { - this.seq = seq; - this.state = state; + public ResponseSendEncryptedPackage(@Nullable Long date, @NotNull List obsoleteKeyGroups, @NotNull List missedKeyGroups) { this.date = date; this.obsoleteKeyGroups = obsoleteKeyGroups; this.missedKeyGroups = missedKeyGroups; @@ -40,16 +36,6 @@ public ResponseSendEncryptedPackage() { } - @Nullable - public Integer getSeq() { - return this.seq; - } - - @Nullable - public byte[] getState() { - return this.state; - } - @Nullable public Long getDate() { return this.date; @@ -61,40 +47,32 @@ public List getObsoleteKeyGroups() { } @NotNull - public List getMissedKeyGroups() { + public List getMissedKeyGroups() { return this.missedKeyGroups; } @Override public void parse(BserValues values) throws IOException { - this.seq = values.optInt(1); - this.state = values.optBytes(2); - this.date = values.optLong(3); + this.date = values.optLong(1); List _obsoleteKeyGroups = new ArrayList(); - for (int i = 0; i < values.getRepeatedCount(4); i ++) { + for (int i = 0; i < values.getRepeatedCount(2); i ++) { _obsoleteKeyGroups.add(new ApiKeyGroupId()); } - this.obsoleteKeyGroups = values.getRepeatedObj(4, _obsoleteKeyGroups); - List _missedKeyGroups = new ArrayList(); - for (int i = 0; i < values.getRepeatedCount(5); i ++) { - _missedKeyGroups.add(new ApiKeyGroupId()); + this.obsoleteKeyGroups = values.getRepeatedObj(2, _obsoleteKeyGroups); + List _missedKeyGroups = new ArrayList(); + for (int i = 0; i < values.getRepeatedCount(3); i ++) { + _missedKeyGroups.add(new ApiKeyGroupHolder()); } - this.missedKeyGroups = values.getRepeatedObj(5, _missedKeyGroups); + this.missedKeyGroups = values.getRepeatedObj(3, _missedKeyGroups); } @Override public void serialize(BserWriter writer) throws IOException { - if (this.seq != null) { - writer.writeInt(1, this.seq); - } - if (this.state != null) { - writer.writeBytes(2, this.state); - } if (this.date != null) { - writer.writeLong(3, this.date); + writer.writeLong(1, this.date); } - writer.writeRepeatedObj(4, this.obsoleteKeyGroups); - writer.writeRepeatedObj(5, this.missedKeyGroups); + writer.writeRepeatedObj(2, this.obsoleteKeyGroups); + writer.writeRepeatedObj(3, this.missedKeyGroups); } @Override diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanDeleteChanged.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanDeleteChanged.java new file mode 100644 index 0000000000..788d88bfcb --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanDeleteChanged.java @@ -0,0 +1,70 @@ +package im.actor.core.api.updates; +/* + * Generated by the Actor API Scheme generator. DO NOT EDIT! + */ + +import im.actor.runtime.bser.*; +import im.actor.runtime.collections.*; +import static im.actor.runtime.bser.Utils.*; +import im.actor.core.network.parser.*; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.NotNull; +import com.google.j2objc.annotations.ObjectiveCName; +import java.io.IOException; +import java.util.List; +import java.util.ArrayList; +import im.actor.core.api.*; + +public class UpdateGroupCanDeleteChanged extends Update { + + public static final int HEADER = 0xa58; + public static UpdateGroupCanDeleteChanged fromBytes(byte[] data) throws IOException { + return Bser.parse(new UpdateGroupCanDeleteChanged(), data); + } + + private int groupId; + private boolean canDeleteChanged; + + public UpdateGroupCanDeleteChanged(int groupId, boolean canDeleteChanged) { + this.groupId = groupId; + this.canDeleteChanged = canDeleteChanged; + } + + public UpdateGroupCanDeleteChanged() { + + } + + public int getGroupId() { + return this.groupId; + } + + public boolean canDeleteChanged() { + return this.canDeleteChanged; + } + + @Override + public void parse(BserValues values) throws IOException { + this.groupId = values.getInt(1); + this.canDeleteChanged = values.getBool(2); + } + + @Override + public void serialize(BserWriter writer) throws IOException { + writer.writeInt(1, this.groupId); + writer.writeBool(2, this.canDeleteChanged); + } + + @Override + public String toString() { + String res = "update GroupCanDeleteChanged{"; + res += "groupId=" + this.groupId; + res += ", canDeleteChanged=" + this.canDeleteChanged; + res += "}"; + return res; + } + + @Override + public int getHeaderKey() { + return HEADER; + } +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanInviteViaLink.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanInviteViaLink.java new file mode 100644 index 0000000000..3f94525a23 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanInviteViaLink.java @@ -0,0 +1,70 @@ +package im.actor.core.api.updates; +/* + * Generated by the Actor API Scheme generator. DO NOT EDIT! + */ + +import im.actor.runtime.bser.*; +import im.actor.runtime.collections.*; +import static im.actor.runtime.bser.Utils.*; +import im.actor.core.network.parser.*; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.NotNull; +import com.google.j2objc.annotations.ObjectiveCName; +import java.io.IOException; +import java.util.List; +import java.util.ArrayList; +import im.actor.core.api.*; + +public class UpdateGroupCanInviteViaLink extends Update { + + public static final int HEADER = 0xa56; + public static UpdateGroupCanInviteViaLink fromBytes(byte[] data) throws IOException { + return Bser.parse(new UpdateGroupCanInviteViaLink(), data); + } + + private int groupId; + private boolean canInviteViaLink; + + public UpdateGroupCanInviteViaLink(int groupId, boolean canInviteViaLink) { + this.groupId = groupId; + this.canInviteViaLink = canInviteViaLink; + } + + public UpdateGroupCanInviteViaLink() { + + } + + public int getGroupId() { + return this.groupId; + } + + public boolean canInviteViaLink() { + return this.canInviteViaLink; + } + + @Override + public void parse(BserValues values) throws IOException { + this.groupId = values.getInt(1); + this.canInviteViaLink = values.getBool(2); + } + + @Override + public void serialize(BserWriter writer) throws IOException { + writer.writeInt(1, this.groupId); + writer.writeBool(2, this.canInviteViaLink); + } + + @Override + public String toString() { + String res = "update GroupCanInviteViaLink{"; + res += "groupId=" + this.groupId; + res += ", canInviteViaLink=" + this.canInviteViaLink; + res += "}"; + return res; + } + + @Override + public int getHeaderKey() { + return HEADER; + } +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanLeaveChanged.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanLeaveChanged.java new file mode 100644 index 0000000000..cea8e92a23 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanLeaveChanged.java @@ -0,0 +1,70 @@ +package im.actor.core.api.updates; +/* + * Generated by the Actor API Scheme generator. DO NOT EDIT! + */ + +import im.actor.runtime.bser.*; +import im.actor.runtime.collections.*; +import static im.actor.runtime.bser.Utils.*; +import im.actor.core.network.parser.*; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.NotNull; +import com.google.j2objc.annotations.ObjectiveCName; +import java.io.IOException; +import java.util.List; +import java.util.ArrayList; +import im.actor.core.api.*; + +public class UpdateGroupCanLeaveChanged extends Update { + + public static final int HEADER = 0xa57; + public static UpdateGroupCanLeaveChanged fromBytes(byte[] data) throws IOException { + return Bser.parse(new UpdateGroupCanLeaveChanged(), data); + } + + private int groupId; + private boolean canLeaveChanged; + + public UpdateGroupCanLeaveChanged(int groupId, boolean canLeaveChanged) { + this.groupId = groupId; + this.canLeaveChanged = canLeaveChanged; + } + + public UpdateGroupCanLeaveChanged() { + + } + + public int getGroupId() { + return this.groupId; + } + + public boolean canLeaveChanged() { + return this.canLeaveChanged; + } + + @Override + public void parse(BserValues values) throws IOException { + this.groupId = values.getInt(1); + this.canLeaveChanged = values.getBool(2); + } + + @Override + public void serialize(BserWriter writer) throws IOException { + writer.writeInt(1, this.groupId); + writer.writeBool(2, this.canLeaveChanged); + } + + @Override + public String toString() { + String res = "update GroupCanLeaveChanged{"; + res += "groupId=" + this.groupId; + res += ", canLeaveChanged=" + this.canLeaveChanged; + res += "}"; + return res; + } + + @Override + public int getHeaderKey() { + return HEADER; + } +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Group.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Group.java index 84919daf2d..a50e9bea3c 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Group.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Group.java @@ -82,6 +82,8 @@ public class Group extends WrapperExtEntity implements K @Property("readonly, nonatomic") private boolean isCanInviteMembers; @Property("readonly, nonatomic") + private boolean isCanInviteViaLink; + @Property("readonly, nonatomic") private boolean isCanViewMembers; @Property("readonly, nonatomic") private boolean isSharedHistory; @@ -95,6 +97,10 @@ public class Group extends WrapperExtEntity implements K private boolean isCanViewAdmins; @Property("readonly, nonatomic") private boolean isCanEditAdmins; + @Property("readonly, nonatomic") + private boolean isCanLeave; + @Property("readonly, nonatomic") + private boolean isCanDelete; @Property("readonly, nonatomic") private boolean haveExtension; @@ -227,6 +233,18 @@ public boolean isCanEditAdmins() { return isCanEditAdmins; } + public boolean isCanInviteViaLink() { + return isCanInviteViaLink; + } + + public boolean isCanLeave() { + return isCanLeave; + } + + public boolean isCanDelete() { + return isCanDelete; + } + public Group updateExt(@Nullable ApiGroupFull ext) { return new Group(getWrapped(), ext); } @@ -344,7 +362,10 @@ public Group editMembers(List members) { e.canEditShortName(), e.canEditAdminList(), e.canViewAdminList(), - e.canEditAdminSettings()); + e.canEditAdminSettings(), + e.canInviteViaLink(), + e.canDelete(), + e.canLeave()); fullExt.setUnmappedObjects(e.getUnmappedObjects()); } @@ -408,7 +429,10 @@ public Group editMembers(List added, List removed, int count e.canEditShortName(), e.canEditAdminList(), e.canViewAdminList(), - e.canEditAdminSettings()); + e.canEditAdminSettings(), + e.canInviteViaLink(), + e.canDelete(), + e.canLeave()); fullExt.setUnmappedObjects(e.getUnmappedObjects()); } @@ -464,7 +488,10 @@ public Group editMembersBecameAsync() { e.canEditShortName(), e.canEditAdminList(), e.canViewAdminList(), - e.canEditAdminSettings()); + e.canEditAdminSettings(), + e.canInviteViaLink(), + e.canDelete(), + e.canLeave()); fullExt.setUnmappedObjects(e.getUnmappedObjects()); return new Group(getWrapped(), fullExt); @@ -504,7 +531,10 @@ public Group editMemberChangedAdmin(int uid, Boolean isAdmin) { e.canEditShortName(), e.canEditAdminList(), e.canViewAdminList(), - e.canEditAdminSettings()); + e.canEditAdminSettings(), + e.canInviteViaLink(), + e.canDelete(), + e.canLeave()); fullExt.setUnmappedObjects(e.getUnmappedObjects()); return new Group(getWrapped(), fullExt); @@ -536,7 +566,10 @@ public Group editTopic(String topic) { e.canEditShortName(), e.canEditAdminList(), e.canViewAdminList(), - e.canEditAdminSettings()); + e.canEditAdminSettings(), + e.canInviteViaLink(), + e.canDelete(), + e.canLeave()); fullExt.setUnmappedObjects(e.getUnmappedObjects()); return new Group(getWrapped(), fullExt); } else { @@ -563,7 +596,10 @@ public Group editAbout(String about) { e.canEditShortName(), e.canEditAdminList(), e.canViewAdminList(), - e.canEditAdminSettings()); + e.canEditAdminSettings(), + e.canInviteViaLink(), + e.canDelete(), + e.canLeave()); fullExt.setUnmappedObjects(e.getUnmappedObjects()); return new Group(getWrapped(), fullExt); } else { @@ -590,7 +626,10 @@ public Group editShortName(String shortName) { e.canEditShortName(), e.canEditAdminList(), e.canViewAdminList(), - e.canEditAdminSettings()); + e.canEditAdminSettings(), + e.canInviteViaLink(), + e.canDelete(), + e.canLeave()); fullExt.setUnmappedObjects(e.getUnmappedObjects()); return new Group(getWrapped(), fullExt); } else { @@ -617,7 +656,10 @@ public Group editFullExt(ApiMapValue ext) { e.canEditShortName(), e.canEditAdminList(), e.canViewAdminList(), - e.canEditAdminSettings()); + e.canEditAdminSettings(), + e.canInviteViaLink(), + e.canDelete(), + e.canLeave()); fullExt.setUnmappedObjects(e.getUnmappedObjects()); return new Group(getWrapped(), fullExt); } else { @@ -644,7 +686,100 @@ public Group editCanViewMembers(boolean canViewMembers) { e.canEditShortName(), e.canEditAdminList(), e.canViewAdminList(), - e.canEditAdminSettings()); + e.canEditAdminSettings(), + e.canInviteViaLink(), + e.canDelete(), + e.canLeave()); + fullExt.setUnmappedObjects(e.getUnmappedObjects()); + return new Group(getWrapped(), fullExt); + } else { + return this; + } + } + + public Group editCanInviteViaLink(boolean canInviteViaLink) { + if (getWrappedExt() != null) { + ApiGroupFull e = getWrappedExt(); + ApiGroupFull fullExt = new ApiGroupFull(e.getId(), + e.getCreateDate(), + e.getOwnerUid(), + e.getMembers(), + e.getTheme(), + e.getAbout(), + e.getExt(), + e.isAsyncMembers(), + e.canViewMembers(), + e.canInvitePeople(), + e.isSharedHistory(), + e.canEditGroupInfo(), + e.getShortName(), + e.canEditShortName(), + e.canEditAdminList(), + e.canViewAdminList(), + e.canEditAdminSettings(), + canInviteViaLink, + e.canDelete(), + e.canLeave()); + fullExt.setUnmappedObjects(e.getUnmappedObjects()); + return new Group(getWrapped(), fullExt); + } else { + return this; + } + } + + public Group editCanDelete(boolean canDelete) { + if (getWrappedExt() != null) { + ApiGroupFull e = getWrappedExt(); + ApiGroupFull fullExt = new ApiGroupFull(e.getId(), + e.getCreateDate(), + e.getOwnerUid(), + e.getMembers(), + e.getTheme(), + e.getAbout(), + e.getExt(), + e.isAsyncMembers(), + e.canViewMembers(), + e.canInvitePeople(), + e.isSharedHistory(), + e.canEditGroupInfo(), + e.getShortName(), + e.canEditShortName(), + e.canEditAdminList(), + e.canViewAdminList(), + e.canEditAdminSettings(), + e.canInviteViaLink(), + canDelete, + e.canLeave()); + fullExt.setUnmappedObjects(e.getUnmappedObjects()); + return new Group(getWrapped(), fullExt); + } else { + return this; + } + } + + public Group editCanLeave(boolean canLeave) { + if (getWrappedExt() != null) { + ApiGroupFull e = getWrappedExt(); + ApiGroupFull fullExt = new ApiGroupFull(e.getId(), + e.getCreateDate(), + e.getOwnerUid(), + e.getMembers(), + e.getTheme(), + e.getAbout(), + e.getExt(), + e.isAsyncMembers(), + e.canViewMembers(), + e.canInvitePeople(), + e.isSharedHistory(), + e.canEditGroupInfo(), + e.getShortName(), + e.canEditShortName(), + e.canEditAdminList(), + e.canViewAdminList(), + e.canEditAdminSettings(), + e.canInviteViaLink(), + e.canDelete(), + canLeave); fullExt.setUnmappedObjects(e.getUnmappedObjects()); return new Group(getWrapped(), fullExt); } else { @@ -671,7 +806,10 @@ public Group editCanEditGroupInfo(boolean canEditGroupInfo) { e.canEditShortName(), e.canEditAdminList(), e.canViewAdminList(), - e.canEditAdminSettings()); + e.canEditAdminSettings(), + e.canInviteViaLink(), + e.canDelete(), + e.canLeave()); fullExt.setUnmappedObjects(e.getUnmappedObjects()); return new Group(getWrapped(), fullExt); } else { @@ -698,7 +836,10 @@ public Group editCanEditShortName(boolean canEditShortName) { canEditShortName, e.canEditAdminList(), e.canViewAdminList(), - e.canEditAdminSettings()); + e.canEditAdminSettings(), + e.canInviteViaLink(), + e.canDelete(), + e.canLeave()); fullExt.setUnmappedObjects(e.getUnmappedObjects()); return new Group(getWrapped(), fullExt); } else { @@ -725,7 +866,10 @@ public Group editCanEditAdminList(boolean canEditAdminList) { e.canEditShortName(), canEditAdminList, e.canViewAdminList(), - e.canEditAdminSettings()); + e.canEditAdminSettings(), + e.canInviteViaLink(), + e.canDelete(), + e.canLeave()); fullExt.setUnmappedObjects(e.getUnmappedObjects()); return new Group(getWrapped(), fullExt); } else { @@ -752,7 +896,10 @@ public Group editCanViewAdminList(boolean canViewAdminList) { e.canEditShortName(), e.canEditAdminList(), canViewAdminList, - e.canEditAdminSettings()); + e.canEditAdminSettings(), + e.canInviteViaLink(), + e.canDelete(), + e.canLeave()); fullExt.setUnmappedObjects(e.getUnmappedObjects()); return new Group(getWrapped(), fullExt); } else { @@ -779,7 +926,10 @@ public Group editCanEditAdminSettings(boolean canEditAdminSettings) { e.canEditShortName(), e.canEditAdminList(), e.canViewAdminList(), - canEditAdminSettings); + canEditAdminSettings, + e.canInviteViaLink(), + e.canDelete(), + e.canLeave()); fullExt.setUnmappedObjects(e.getUnmappedObjects()); return new Group(getWrapped(), fullExt); } else { @@ -806,7 +956,10 @@ public Group editCanInviteMembers(boolean canInviteMembers) { e.canEditShortName(), e.canEditAdminList(), e.canViewAdminList(), - e.canEditAdminSettings()); + e.canEditAdminSettings(), + e.canInviteViaLink(), + e.canDelete(), + e.canLeave()); fullExt.setUnmappedObjects(e.getUnmappedObjects()); return new Group(getWrapped(), fullExt); } else { @@ -833,7 +986,10 @@ public Group editOwner(int uid) { e.canEditShortName(), e.canEditAdminList(), e.canViewAdminList(), - e.canEditAdminSettings()); + e.canEditAdminSettings(), + e.canInviteViaLink(), + e.canDelete(), + e.canLeave()); fullExt.setUnmappedObjects(e.getUnmappedObjects()); return new Group(getWrapped(), fullExt); } else { @@ -860,7 +1016,10 @@ public Group editHistoryShared() { e.canEditShortName(), e.canEditAdminList(), e.canViewAdminList(), - e.canEditAdminSettings()); + e.canEditAdminSettings(), + e.canInviteViaLink(), + e.canDelete(), + e.canLeave()); fullExt.setUnmappedObjects(e.getUnmappedObjects()); return new Group(getWrapped(), fullExt); } else { @@ -917,12 +1076,15 @@ protected void applyWrapped(@NotNull ApiGroup wrapped, @Nullable ApiGroupFull ex this.isAsyncMembers = ext.isAsyncMembers() != null ? ext.isAsyncMembers() : false; this.isCanViewMembers = ext.canViewMembers() != null ? ext.canViewMembers() : true; this.isCanInviteMembers = ext.canViewMembers() != null ? ext.canViewMembers() : true; + this.isCanInviteViaLink = ext.canInviteViaLink() != null ? ext.canInviteViaLink() : false; this.isSharedHistory = ext.isSharedHistory() != null ? ext.isSharedHistory() : false; this.isCanEditInfo = ext.canEditGroupInfo() != null ? ext.canEditGroupInfo() : false; this.isCanEditShortName = ext.canEditShortName() != null ? ext.canEditShortName() : false; this.isCanEditAdministration = ext.canEditAdminSettings() != null ? ext.canEditAdminSettings() : false; this.isCanViewAdmins = ext.canViewAdminList() != null ? ext.canViewAdminList() : false; this.isCanEditAdmins = ext.canEditAdminList() != null ? ext.canEditAdminList() : false; + this.isCanLeave = ext.canLeave() != null ? ext.canLeave() : true; + this.isCanDelete = ext.canDelete() != null ? ext.canDelete() : false; this.members = new ArrayList<>(); for (ApiMember m : ext.getMembers()) { @@ -945,6 +1107,9 @@ protected void applyWrapped(@NotNull ApiGroup wrapped, @Nullable ApiGroupFull ex this.isCanEditAdministration = false; this.isCanViewAdmins = false; this.isCanEditAdmins = false; + this.isCanDelete = false; + this.isCanLeave = false; + this.isCanInviteViaLink = false; } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsModule.java index ac3b047ac0..27fc3cac74 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsModule.java @@ -15,6 +15,7 @@ import im.actor.core.api.ApiPeerType; import im.actor.core.api.ApiUserOutPeer; import im.actor.core.api.rpc.RequestCreateGroup; +import im.actor.core.api.rpc.RequestDeleteGroup; import im.actor.core.api.rpc.RequestEditGroupAbout; import im.actor.core.api.rpc.RequestEditGroupShortName; import im.actor.core.api.rpc.RequestEditGroupTitle; @@ -31,6 +32,7 @@ import im.actor.core.api.rpc.RequestRevokeIntegrationToken; import im.actor.core.api.rpc.RequestRevokeInviteUrl; import im.actor.core.api.rpc.RequestSaveAdminSettings; +import im.actor.core.api.rpc.RequestShareHistory; import im.actor.core.api.rpc.RequestTransferOwnership; import im.actor.core.api.rpc.ResponseIntegrationToken; import im.actor.core.api.rpc.ResponseInviteUrl; @@ -187,6 +189,20 @@ public Promise leaveGroup(final int gid) { .flatMap(r -> updates().waitForUpdate(r.getSeq())); } + public Promise deleteGroup(int gid) { + return getGroups().getValueAsync(gid) + .flatMap(group -> + api(new RequestDeleteGroup(new ApiGroupOutPeer(group.getGroupId(), group.getAccessHash())))) + .flatMap(r -> updates().waitForUpdate(r.getSeq())); + } + + public Promise shareHistory(int gid) { + return getGroups().getValueAsync(gid) + .flatMap(group -> + api(new RequestShareHistory(new ApiGroupOutPeer(group.getGroupId(), group.getAccessHash())))) + .flatMap(r -> updates().waitForUpdate(r.getSeq())); + } + public Promise makeAdmin(final int gid, final int uid) { return Promises.tuple(getGroups().getValueAsync(gid), users().getValueAsync(uid)) .flatMap(groupUserTuple2 -> api(new RequestMakeUserAdminObsolete( diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsProcessor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsProcessor.java index bbb2f41700..18559a6fef 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsProcessor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsProcessor.java @@ -6,10 +6,13 @@ import im.actor.core.api.updates.UpdateGroupAboutChanged; import im.actor.core.api.updates.UpdateGroupAvatarChanged; +import im.actor.core.api.updates.UpdateGroupCanDeleteChanged; import im.actor.core.api.updates.UpdateGroupCanEditAdminsChanged; import im.actor.core.api.updates.UpdateGroupCanEditInfoChanged; import im.actor.core.api.updates.UpdateGroupCanEditUsernameChanged; import im.actor.core.api.updates.UpdateGroupCanInviteMembersChanged; +import im.actor.core.api.updates.UpdateGroupCanInviteViaLink; +import im.actor.core.api.updates.UpdateGroupCanLeaveChanged; import im.actor.core.api.updates.UpdateGroupCanSendMessagesChanged; import im.actor.core.api.updates.UpdateGroupCanViewAdminsChanged; import im.actor.core.api.updates.UpdateGroupCanViewMembersChanged; @@ -65,7 +68,10 @@ public Promise process(Update update) { update instanceof UpdateGroupCanEditInfoChanged || update instanceof UpdateGroupCanViewAdminsChanged || update instanceof UpdateGroupCanEditAdminsChanged || - update instanceof UpdateGroupCanEditUsernameChanged) { + update instanceof UpdateGroupCanEditUsernameChanged || + update instanceof UpdateGroupCanInviteViaLink || + update instanceof UpdateGroupCanLeaveChanged || + update instanceof UpdateGroupCanDeleteChanged) { return context().getGroupsModule().getRouter().onUpdate(update); } return null; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/router/GroupRouter.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/router/GroupRouter.java index 1bf10431c3..923e7ff2d2 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/router/GroupRouter.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/router/GroupRouter.java @@ -14,11 +14,14 @@ import im.actor.core.api.rpc.RequestLoadFullGroups; import im.actor.core.api.updates.UpdateGroupAboutChanged; import im.actor.core.api.updates.UpdateGroupAvatarChanged; +import im.actor.core.api.updates.UpdateGroupCanDeleteChanged; import im.actor.core.api.updates.UpdateGroupCanEditAdminSettingsChanged; import im.actor.core.api.updates.UpdateGroupCanEditAdminsChanged; import im.actor.core.api.updates.UpdateGroupCanEditInfoChanged; import im.actor.core.api.updates.UpdateGroupCanEditUsernameChanged; import im.actor.core.api.updates.UpdateGroupCanInviteMembersChanged; +import im.actor.core.api.updates.UpdateGroupCanInviteViaLink; +import im.actor.core.api.updates.UpdateGroupCanLeaveChanged; import im.actor.core.api.updates.UpdateGroupCanSendMessagesChanged; import im.actor.core.api.updates.UpdateGroupCanViewAdminsChanged; import im.actor.core.api.updates.UpdateGroupCanViewMembersChanged; @@ -180,6 +183,21 @@ public Promise onEditCanEditAdminSettings(int groupId, boolean canEditAdmi return editGroup(groupId, group -> group.editCanEditAdminSettings(canEditAdminSettings)); } + @Verified + public Promise onEditCanLeaveChanged(int groupId, boolean canLeave) { + return editGroup(groupId, group -> group.editCanLeave(canLeave)); + } + + @Verified + public Promise onEditCanDeleteChanged(int groupId, boolean canDelete) { + return editGroup(groupId, group -> group.editCanLeave(canDelete)); + } + + @Verified + public Promise onEditCanInviteViaLinkChanged(int groupId, boolean canInvite) { + return editGroup(groupId, group -> group.editCanInviteViaLink(canInvite)); + } + @Verified public Promise onOwnerChanged(int groupId, int updatedOwner) { return editGroup(groupId, group -> group.editOwner(updatedOwner)); @@ -406,6 +424,15 @@ else if (update instanceof UpdateGroupCanSendMessagesChanged) { } else if (update instanceof UpdateGroupCanEditAdminSettingsChanged) { UpdateGroupCanEditAdminSettingsChanged settings = (UpdateGroupCanEditAdminSettingsChanged) update; return onEditCanEditAdminSettings(settings.getGroupId(), settings.canEditAdminSettings()); + } else if (update instanceof UpdateGroupCanLeaveChanged) { + UpdateGroupCanLeaveChanged canLeaveChanged = (UpdateGroupCanLeaveChanged) update; + return onEditCanLeaveChanged(canLeaveChanged.getGroupId(), canLeaveChanged.canLeaveChanged()); + } else if (update instanceof UpdateGroupCanDeleteChanged) { + UpdateGroupCanDeleteChanged canDeleteChanged = (UpdateGroupCanDeleteChanged) update; + return onEditCanDeleteChanged(canDeleteChanged.getGroupId(), canDeleteChanged.canDeleteChanged()); + } else if (update instanceof UpdateGroupCanInviteViaLink) { + UpdateGroupCanInviteViaLink inviteViaLink = (UpdateGroupCanInviteViaLink) update; + return onEditCanInviteViaLinkChanged(inviteViaLink.getGroupId(), inviteViaLink.canInviteViaLink()); } return Promise.success(null); diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java index d9bc3b9179..91c9a4de22 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java @@ -84,7 +84,15 @@ public class GroupVM extends BaseValueModel { @NotNull @Property("nonatomic, readonly") private BooleanValueModel isCanViewAdmins; - + @NotNull + @Property("nonatomic, readonly") + private BooleanValueModel isCanLeave; + @NotNull + @Property("nonatomic, readonly") + private BooleanValueModel isCanDelete; + @NotNull + @Property("nonatomic, readonly") + private BooleanValueModel isCanInviteViaLink; @NotNull @Property("nonatomic, readonly") @@ -129,6 +137,9 @@ public GroupVM(@NotNull Group rawObj) { this.isHistoryShared = new BooleanValueModel("group." + groupId + ".isHistoryShared", rawObj.isSharedHistory()); this.isCanEditAdmins = new BooleanValueModel("group." + groupId + ".isCanEditAdmins", rawObj.isCanEditAdmins()); this.isCanViewAdmins = new BooleanValueModel("group." + groupId + ".isCanViewAdmins", rawObj.isCanViewAdmins()); + this.isCanLeave = new BooleanValueModel("group." + groupId + ".isCanLeave", rawObj.isCanLeave()); + this.isCanDelete = new BooleanValueModel("group." + groupId + ".isCanDelete", rawObj.isCanDelete()); + this.isCanInviteViaLink = new BooleanValueModel("group." + groupId + ".isCanInviteViaLink", rawObj.isCanInviteViaLink()); this.ownerId = new IntValueModel("group." + groupId + ".membersCount", rawObj.getOwnerId()); this.members = new ValueModel<>("group." + groupId + ".members", new HashSet<>(rawObj.getMembers())); @@ -325,6 +336,39 @@ public BooleanValueModel getIsCanEditShortName() { return isCanEditShortName; } + /** + * Is current user can leave group + * + * @return is current user can leave model + */ + @NotNull + @ObjectiveCName("getIsCanLeaveModel") + public BooleanValueModel getIsCanLeave() { + return isCanLeave; + } + + /** + * Is current user can delete group + * + * @return is current user can delete model + */ + @NotNull + @ObjectiveCName("getIsCanDeleteModel") + public BooleanValueModel getIsCanDelete() { + return isCanDelete; + } + + /** + * Is current user can invite via link + * + * @return is current user can invite via link model + */ + @NotNull + @ObjectiveCName("getIsCanInviteViaLinkModel") + public BooleanValueModel getIsCanInviteViaLink() { + return isCanInviteViaLink; + } + /** * Get Group owner user id model * @@ -427,6 +471,9 @@ protected void updateValues(@NotNull Group rawObj) { isChanged |= isCanEditAdministration.change(rawObj.isCanEditAdministration()); isChanged |= isCanEditAdmins.change(rawObj.isCanEditAdmins()); isChanged |= isCanViewAdmins.change(rawObj.isCanViewAdmins()); + isChanged |= isCanLeave.change(rawObj.isCanLeave()); + isChanged |= isCanDelete.change(rawObj.isCanDelete()); + isChanged |= isCanInviteViaLink.change(rawObj.isCanInviteViaLink()); if (isChanged) { notifyIfNeeded(); From fce6371584c5f5a02f5163ee0b1fcf762c13b494 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Wed, 20 Jul 2016 16:15:39 +0300 Subject: [PATCH 068/414] fix(iOS): Using built-in CocoaAsyncSocket --- actor-sdk/sdk-core-ios/ActorSDK-iOS.podspec | 1 - .../ActorSDK.xcodeproj/project.pbxproj | 16 + .../Providers/CocoaNetworkRuntime.swift | 1 - .../sdk-core-ios/ActorSDK/Sources/ActorSDK.h | 17 +- .../ActorSDK/Sources/GCDAsyncSocket.h | 1210 +++ .../ActorSDK/Sources/GCDAsyncSocket.m | 8365 +++++++++++++++++ actor-sdk/sdk-core-ios/Podfile | 4 +- 7 files changed, 9604 insertions(+), 10 deletions(-) create mode 100755 actor-sdk/sdk-core-ios/ActorSDK/Sources/GCDAsyncSocket.h create mode 100755 actor-sdk/sdk-core-ios/ActorSDK/Sources/GCDAsyncSocket.m diff --git a/actor-sdk/sdk-core-ios/ActorSDK-iOS.podspec b/actor-sdk/sdk-core-ios/ActorSDK-iOS.podspec index 36f1bd8a23..9a4fe8b85c 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK-iOS.podspec +++ b/actor-sdk/sdk-core-ios/ActorSDK-iOS.podspec @@ -16,7 +16,6 @@ Pod::Spec.new do |s| # Core s.dependency 'RegexKitLite' - s.dependency 'CocoaAsyncSocket' s.dependency 'zipzap' s.dependency 'J2ObjC-Framework' diff --git a/actor-sdk/sdk-core-ios/ActorSDK.xcodeproj/project.pbxproj b/actor-sdk/sdk-core-ios/ActorSDK.xcodeproj/project.pbxproj index 5cd8e6695a..1b08a8b613 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK.xcodeproj/project.pbxproj +++ b/actor-sdk/sdk-core-ios/ActorSDK.xcodeproj/project.pbxproj @@ -233,6 +233,8 @@ 069CF4D11BCB909A00C66E12 /* CLTokenInputView.m in Sources */ = {isa = PBXBuildFile; fileRef = 069CF4C91BCB909A00C66E12 /* CLTokenInputView.m */; }; 069CF4D21BCB909A00C66E12 /* CLTokenView.h in Headers */ = {isa = PBXBuildFile; fileRef = 069CF4CA1BCB909A00C66E12 /* CLTokenView.h */; settings = {ATTRIBUTES = (Public, ); }; }; 069CF4D31BCB909A00C66E12 /* CLTokenView.m in Sources */ = {isa = PBXBuildFile; fileRef = 069CF4CB1BCB909A00C66E12 /* CLTokenView.m */; }; + 06ABFE331D3FAF800031A0D6 /* GCDAsyncSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 06ABFE2F1D3FAF800031A0D6 /* GCDAsyncSocket.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 06ABFE341D3FAF800031A0D6 /* GCDAsyncSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 06ABFE301D3FAF800031A0D6 /* GCDAsyncSocket.m */; }; 06B489ED1C9F6EBD0054245B /* AAStickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06B489EC1C9F6EBC0054245B /* AAStickerView.swift */; }; 06C1D0771C8BC9FC00B73632 /* AAAuthNameViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06C1D0761C8BC9FC00B73632 /* AAAuthNameViewController.swift */; }; 06C1D07B1C8BFE5C00B73632 /* AAAuthPhoneViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06C1D07A1C8BFE5C00B73632 /* AAAuthPhoneViewController.swift */; }; @@ -605,6 +607,8 @@ 069CF4C91BCB909A00C66E12 /* CLTokenInputView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CLTokenInputView.m; sourceTree = ""; }; 069CF4CA1BCB909A00C66E12 /* CLTokenView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CLTokenView.h; sourceTree = ""; }; 069CF4CB1BCB909A00C66E12 /* CLTokenView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CLTokenView.m; sourceTree = ""; }; + 06ABFE2F1D3FAF800031A0D6 /* GCDAsyncSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDAsyncSocket.h; sourceTree = ""; }; + 06ABFE301D3FAF800031A0D6 /* GCDAsyncSocket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDAsyncSocket.m; sourceTree = ""; }; 06B489EC1C9F6EBC0054245B /* AAStickerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AAStickerView.swift; sourceTree = ""; }; 06C1D0761C8BC9FC00B73632 /* AAAuthNameViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AAAuthNameViewController.swift; sourceTree = ""; }; 06C1D07A1C8BFE5C00B73632 /* AAAuthPhoneViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AAAuthPhoneViewController.swift; sourceTree = ""; }; @@ -1416,6 +1420,15 @@ path = CLTokenInputView; sourceTree = ""; }; + 06ABFE261D3FAF1A0031A0D6 /* CocoaAsyncSocket */ = { + isa = PBXGroup; + children = ( + 06ABFE2F1D3FAF800031A0D6 /* GCDAsyncSocket.h */, + 06ABFE301D3FAF800031A0D6 /* GCDAsyncSocket.m */, + ); + name = CocoaAsyncSocket; + sourceTree = ""; + }; 06C1D0751C8BC55100B73632 /* Welcome */ = { isa = PBXGroup; children = ( @@ -1436,6 +1449,7 @@ 06E322CB1C69392F00D66F53 /* Libs */ = { isa = PBXGroup; children = ( + 06ABFE261D3FAF1A0031A0D6 /* CocoaAsyncSocket */, 06E164921C96FF15005AFB94 /* CommonCrypto */, 061850A31C95CBF000C522D5 /* YYKit */, 15D35F0A1C20182900E3717A /* AudioRecorder */, @@ -1813,6 +1827,7 @@ 152AA8AA1C2989270030DEEE /* SLKTextView.h in Headers */, 152AA8B01C2989270030DEEE /* SLKTypingIndicatorProtocol.h in Headers */, 152AA8AC1C2989270030DEEE /* SLKTextView+SLKAdditions.h in Headers */, + 06ABFE331D3FAF800031A0D6 /* GCDAsyncSocket.h in Headers */, 152AA8AE1C2989270030DEEE /* SLKTextViewController.h in Headers */, 152AA8B31C2989270030DEEE /* SLKUIConstants.h in Headers */, 152AA8B61C2989270030DEEE /* UIScrollView+SLKAdditions.h in Headers */, @@ -2114,6 +2129,7 @@ 066A51241BC4B56D000E606E /* Colors.swift in Sources */, 066A53171BC533DD000E606E /* AABubbles.swift in Sources */, 153F6B8B1C2D7BA400C0B960 /* AATapLabel.swift in Sources */, + 06ABFE341D3FAF800031A0D6 /* GCDAsyncSocket.m in Sources */, 061850E71C95CBF000C522D5 /* YYTextLine.m in Sources */, 066A52081BC4E962000E606E /* Makefile in Sources */, 066A53231BC533F5000E606E /* Caches.swift in Sources */, diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/CocoaNetworkRuntime.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/CocoaNetworkRuntime.swift index 652580f833..84249dc4f8 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/CocoaNetworkRuntime.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/CocoaNetworkRuntime.swift @@ -3,7 +3,6 @@ // import Foundation -import CocoaAsyncSocket class CocoaNetworkRuntime : ARManagedNetworkProvider { diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorSDK.h b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorSDK.h index af900c424c..d9f943826d 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorSDK.h +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorSDK.h @@ -30,27 +30,32 @@ FOUNDATION_EXPORT const unsigned char ActorSDKVersionString[]; #import "FMDatabaseAdditions.h" #import "FMDatabaseQueue.h" -#import "CLTokenInputView.h" +// GCDAsyncSocket -#import "CLTokenInputView.h" +#import "GCDAsyncSocket.h" // Ogg record +#import "AAAudioRecorder.h" +#import "AAAudioPlayer.h" +#import "AAModernConversationAudioPlayer.h" + +// SLKTextViewController + #import "SLKTextViewController.h" +// NYTPhotos + #import "NYTPhotosViewController.h" #import "NYTPhoto.h" #import "NYTPhotoViewController.h" #import "NYTPhotosViewControllerDataSource.h" #import "NYTPhotoCaptionViewLayoutWidthHinting.h" -#import "AAAudioRecorder.h" -#import "AAAudioPlayer.h" -#import "AAModernConversationAudioPlayer.h" - // CLTokenView #import "CLTokenView.h" +#import "CLTokenInputView.h" // YYKit diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/GCDAsyncSocket.h b/actor-sdk/sdk-core-ios/ActorSDK/Sources/GCDAsyncSocket.h new file mode 100755 index 0000000000..6c4f04095c --- /dev/null +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/GCDAsyncSocket.h @@ -0,0 +1,1210 @@ +// +// GCDAsyncSocket.h +// +// This class is in the public domain. +// Originally created by Robbie Hanson in Q3 2010. +// Updated and maintained by Deusty LLC and the Apple development community. +// +// https://github.com/robbiehanson/CocoaAsyncSocket +// + +#import +#import +#import +#import +#import + +#include // AF_INET, AF_INET6 + +@class GCDAsyncReadPacket; +@class GCDAsyncWritePacket; +@class GCDAsyncSocketPreBuffer; +@protocol GCDAsyncSocketDelegate; + +NS_ASSUME_NONNULL_BEGIN + +extern NSString *const GCDAsyncSocketException; +extern NSString *const GCDAsyncSocketErrorDomain; + +extern NSString *const GCDAsyncSocketQueueName; +extern NSString *const GCDAsyncSocketThreadName; + +extern NSString *const GCDAsyncSocketManuallyEvaluateTrust; +#if TARGET_OS_IPHONE +extern NSString *const GCDAsyncSocketUseCFStreamForTLS; +#endif +#define GCDAsyncSocketSSLPeerName (NSString *)kCFStreamSSLPeerName +#define GCDAsyncSocketSSLCertificates (NSString *)kCFStreamSSLCertificates +#define GCDAsyncSocketSSLIsServer (NSString *)kCFStreamSSLIsServer +extern NSString *const GCDAsyncSocketSSLPeerID; +extern NSString *const GCDAsyncSocketSSLProtocolVersionMin; +extern NSString *const GCDAsyncSocketSSLProtocolVersionMax; +extern NSString *const GCDAsyncSocketSSLSessionOptionFalseStart; +extern NSString *const GCDAsyncSocketSSLSessionOptionSendOneByteRecord; +extern NSString *const GCDAsyncSocketSSLCipherSuites; +#if !TARGET_OS_IPHONE +extern NSString *const GCDAsyncSocketSSLDiffieHellmanParameters; +#endif + +#define GCDAsyncSocketLoggingContext 65535 + + +typedef NS_ENUM(NSInteger, GCDAsyncSocketError) { + GCDAsyncSocketNoError = 0, // Never used + GCDAsyncSocketBadConfigError, // Invalid configuration + GCDAsyncSocketBadParamError, // Invalid parameter was passed + GCDAsyncSocketConnectTimeoutError, // A connect operation timed out + GCDAsyncSocketReadTimeoutError, // A read operation timed out + GCDAsyncSocketWriteTimeoutError, // A write operation timed out + GCDAsyncSocketReadMaxedOutError, // Reached set maxLength without completing + GCDAsyncSocketClosedError, // The remote peer closed the connection + GCDAsyncSocketOtherError, // Description provided in userInfo +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + +@interface GCDAsyncSocket : NSObject + +/** + * GCDAsyncSocket uses the standard delegate paradigm, + * but executes all delegate callbacks on a given delegate dispatch queue. + * This allows for maximum concurrency, while at the same time providing easy thread safety. + * + * You MUST set a delegate AND delegate dispatch queue before attempting to + * use the socket, or you will get an error. + * + * The socket queue is optional. + * If you pass NULL, GCDAsyncSocket will automatically create it's own socket queue. + * If you choose to provide a socket queue, the socket queue must not be a concurrent queue. + * If you choose to provide a socket queue, and the socket queue has a configured target queue, + * then please see the discussion for the method markSocketQueueTargetQueue. + * + * The delegate queue and socket queue can optionally be the same. +**/ +- (instancetype)init; +- (instancetype)initWithSocketQueue:(nullable dispatch_queue_t)sq; +- (instancetype)initWithDelegate:(nullable id)aDelegate delegateQueue:(nullable dispatch_queue_t)dq; +- (instancetype)initWithDelegate:(nullable id)aDelegate delegateQueue:(nullable dispatch_queue_t)dq socketQueue:(nullable dispatch_queue_t)sq; + +#pragma mark Configuration + +@property (atomic, weak, readwrite, nullable) id delegate; +#if OS_OBJECT_USE_OBJC +@property (atomic, strong, readwrite, nullable) dispatch_queue_t delegateQueue; +#else +@property (atomic, assign, readwrite, nullable) dispatch_queue_t delegateQueue; +#endif + +- (void)getDelegate:(id __nullable * __nullable)delegatePtr delegateQueue:(dispatch_queue_t __nullable * __nullable)delegateQueuePtr; +- (void)setDelegate:(nullable id)delegate delegateQueue:(nullable dispatch_queue_t)delegateQueue; + +/** + * If you are setting the delegate to nil within the delegate's dealloc method, + * you may need to use the synchronous versions below. +**/ +- (void)synchronouslySetDelegate:(nullable id)delegate; +- (void)synchronouslySetDelegateQueue:(nullable dispatch_queue_t)delegateQueue; +- (void)synchronouslySetDelegate:(nullable id)delegate delegateQueue:(nullable dispatch_queue_t)delegateQueue; + +/** + * By default, both IPv4 and IPv6 are enabled. + * + * For accepting incoming connections, this means GCDAsyncSocket automatically supports both protocols, + * and can simulataneously accept incoming connections on either protocol. + * + * For outgoing connections, this means GCDAsyncSocket can connect to remote hosts running either protocol. + * If a DNS lookup returns only IPv4 results, GCDAsyncSocket will automatically use IPv4. + * If a DNS lookup returns only IPv6 results, GCDAsyncSocket will automatically use IPv6. + * If a DNS lookup returns both IPv4 and IPv6 results, the preferred protocol will be chosen. + * By default, the preferred protocol is IPv4, but may be configured as desired. +**/ + +@property (atomic, assign, readwrite, getter=isIPv4Enabled) BOOL IPv4Enabled; +@property (atomic, assign, readwrite, getter=isIPv6Enabled) BOOL IPv6Enabled; + +@property (atomic, assign, readwrite, getter=isIPv4PreferredOverIPv6) BOOL IPv4PreferredOverIPv6; + +/** + * When connecting to both IPv4 and IPv6 using Happy Eyeballs (RFC 6555) https://tools.ietf.org/html/rfc6555 + * this is the delay between connecting to the preferred protocol and the fallback protocol. + * + * Defaults to 300ms. +**/ +@property (atomic, assign, readwrite) NSTimeInterval alternateAddressDelay; + +/** + * User data allows you to associate arbitrary information with the socket. + * This data is not used internally by socket in any way. +**/ +@property (atomic, strong, readwrite, nullable) id userData; + +#pragma mark Accepting + +/** + * Tells the socket to begin listening and accepting connections on the given port. + * When a connection is accepted, a new instance of GCDAsyncSocket will be spawned to handle it, + * and the socket:didAcceptNewSocket: delegate method will be invoked. + * + * The socket will listen on all available interfaces (e.g. wifi, ethernet, etc) +**/ +- (BOOL)acceptOnPort:(uint16_t)port error:(NSError **)errPtr; + +/** + * This method is the same as acceptOnPort:error: with the + * additional option of specifying which interface to listen on. + * + * For example, you could specify that the socket should only accept connections over ethernet, + * and not other interfaces such as wifi. + * + * The interface may be specified by name (e.g. "en1" or "lo0") or by IP address (e.g. "192.168.4.34"). + * You may also use the special strings "localhost" or "loopback" to specify that + * the socket only accept connections from the local machine. + * + * You can see the list of interfaces via the command line utility "ifconfig", + * or programmatically via the getifaddrs() function. + * + * To accept connections on any interface pass nil, or simply use the acceptOnPort:error: method. +**/ +- (BOOL)acceptOnInterface:(nullable NSString *)interface port:(uint16_t)port error:(NSError **)errPtr; + +/** + * Tells the socket to begin listening and accepting connections on the unix domain at the given url. + * When a connection is accepted, a new instance of GCDAsyncSocket will be spawned to handle it, + * and the socket:didAcceptNewSocket: delegate method will be invoked. + * + * The socket will listen on all available interfaces (e.g. wifi, ethernet, etc) + **/ +- (BOOL)acceptOnUrl:(NSURL *)url error:(NSError **)errPtr; + +#pragma mark Connecting + +/** + * Connects to the given host and port. + * + * This method invokes connectToHost:onPort:viaInterface:withTimeout:error: + * and uses the default interface, and no timeout. +**/ +- (BOOL)connectToHost:(NSString *)host onPort:(uint16_t)port error:(NSError **)errPtr; + +/** + * Connects to the given host and port with an optional timeout. + * + * This method invokes connectToHost:onPort:viaInterface:withTimeout:error: and uses the default interface. +**/ +- (BOOL)connectToHost:(NSString *)host + onPort:(uint16_t)port + withTimeout:(NSTimeInterval)timeout + error:(NSError **)errPtr; + +/** + * Connects to the given host & port, via the optional interface, with an optional timeout. + * + * The host may be a domain name (e.g. "deusty.com") or an IP address string (e.g. "192.168.0.2"). + * The host may also be the special strings "localhost" or "loopback" to specify connecting + * to a service on the local machine. + * + * The interface may be a name (e.g. "en1" or "lo0") or the corresponding IP address (e.g. "192.168.4.35"). + * The interface may also be used to specify the local port (see below). + * + * To not time out use a negative time interval. + * + * This method will return NO if an error is detected, and set the error pointer (if one was given). + * Possible errors would be a nil host, invalid interface, or socket is already connected. + * + * If no errors are detected, this method will start a background connect operation and immediately return YES. + * The delegate callbacks are used to notify you when the socket connects, or if the host was unreachable. + * + * Since this class supports queued reads and writes, you can immediately start reading and/or writing. + * All read/write operations will be queued, and upon socket connection, + * the operations will be dequeued and processed in order. + * + * The interface may optionally contain a port number at the end of the string, separated by a colon. + * This allows you to specify the local port that should be used for the outgoing connection. (read paragraph to end) + * To specify both interface and local port: "en1:8082" or "192.168.4.35:2424". + * To specify only local port: ":8082". + * Please note this is an advanced feature, and is somewhat hidden on purpose. + * You should understand that 99.999% of the time you should NOT specify the local port for an outgoing connection. + * If you think you need to, there is a very good chance you have a fundamental misunderstanding somewhere. + * Local ports do NOT need to match remote ports. In fact, they almost never do. + * This feature is here for networking professionals using very advanced techniques. +**/ +- (BOOL)connectToHost:(NSString *)host + onPort:(uint16_t)port + viaInterface:(nullable NSString *)interface + withTimeout:(NSTimeInterval)timeout + error:(NSError **)errPtr; + +/** + * Connects to the given address, specified as a sockaddr structure wrapped in a NSData object. + * For example, a NSData object returned from NSNetService's addresses method. + * + * If you have an existing struct sockaddr you can convert it to a NSData object like so: + * struct sockaddr sa -> NSData *dsa = [NSData dataWithBytes:&remoteAddr length:remoteAddr.sa_len]; + * struct sockaddr *sa -> NSData *dsa = [NSData dataWithBytes:remoteAddr length:remoteAddr->sa_len]; + * + * This method invokes connectToAdd +**/ +- (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr; + +/** + * This method is the same as connectToAddress:error: with an additional timeout option. + * To not time out use a negative time interval, or simply use the connectToAddress:error: method. +**/ +- (BOOL)connectToAddress:(NSData *)remoteAddr withTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr; + +/** + * Connects to the given address, using the specified interface and timeout. + * + * The address is specified as a sockaddr structure wrapped in a NSData object. + * For example, a NSData object returned from NSNetService's addresses method. + * + * If you have an existing struct sockaddr you can convert it to a NSData object like so: + * struct sockaddr sa -> NSData *dsa = [NSData dataWithBytes:&remoteAddr length:remoteAddr.sa_len]; + * struct sockaddr *sa -> NSData *dsa = [NSData dataWithBytes:remoteAddr length:remoteAddr->sa_len]; + * + * The interface may be a name (e.g. "en1" or "lo0") or the corresponding IP address (e.g. "192.168.4.35"). + * The interface may also be used to specify the local port (see below). + * + * The timeout is optional. To not time out use a negative time interval. + * + * This method will return NO if an error is detected, and set the error pointer (if one was given). + * Possible errors would be a nil host, invalid interface, or socket is already connected. + * + * If no errors are detected, this method will start a background connect operation and immediately return YES. + * The delegate callbacks are used to notify you when the socket connects, or if the host was unreachable. + * + * Since this class supports queued reads and writes, you can immediately start reading and/or writing. + * All read/write operations will be queued, and upon socket connection, + * the operations will be dequeued and processed in order. + * + * The interface may optionally contain a port number at the end of the string, separated by a colon. + * This allows you to specify the local port that should be used for the outgoing connection. (read paragraph to end) + * To specify both interface and local port: "en1:8082" or "192.168.4.35:2424". + * To specify only local port: ":8082". + * Please note this is an advanced feature, and is somewhat hidden on purpose. + * You should understand that 99.999% of the time you should NOT specify the local port for an outgoing connection. + * If you think you need to, there is a very good chance you have a fundamental misunderstanding somewhere. + * Local ports do NOT need to match remote ports. In fact, they almost never do. + * This feature is here for networking professionals using very advanced techniques. +**/ +- (BOOL)connectToAddress:(NSData *)remoteAddr + viaInterface:(nullable NSString *)interface + withTimeout:(NSTimeInterval)timeout + error:(NSError **)errPtr; +/** + * Connects to the unix domain socket at the given url, using the specified timeout. + */ +- (BOOL)connectToUrl:(NSURL *)url withTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr; + +#pragma mark Disconnecting + +/** + * Disconnects immediately (synchronously). Any pending reads or writes are dropped. + * + * If the socket is not already disconnected, an invocation to the socketDidDisconnect:withError: delegate method + * will be queued onto the delegateQueue asynchronously (behind any previously queued delegate methods). + * In other words, the disconnected delegate method will be invoked sometime shortly after this method returns. + * + * Please note the recommended way of releasing a GCDAsyncSocket instance (e.g. in a dealloc method) + * [asyncSocket setDelegate:nil]; + * [asyncSocket disconnect]; + * [asyncSocket release]; + * + * If you plan on disconnecting the socket, and then immediately asking it to connect again, + * you'll likely want to do so like this: + * [asyncSocket setDelegate:nil]; + * [asyncSocket disconnect]; + * [asyncSocket setDelegate:self]; + * [asyncSocket connect...]; +**/ +- (void)disconnect; + +/** + * Disconnects after all pending reads have completed. + * After calling this, the read and write methods will do nothing. + * The socket will disconnect even if there are still pending writes. +**/ +- (void)disconnectAfterReading; + +/** + * Disconnects after all pending writes have completed. + * After calling this, the read and write methods will do nothing. + * The socket will disconnect even if there are still pending reads. +**/ +- (void)disconnectAfterWriting; + +/** + * Disconnects after all pending reads and writes have completed. + * After calling this, the read and write methods will do nothing. +**/ +- (void)disconnectAfterReadingAndWriting; + +#pragma mark Diagnostics + +/** + * Returns whether the socket is disconnected or connected. + * + * A disconnected socket may be recycled. + * That is, it can used again for connecting or listening. + * + * If a socket is in the process of connecting, it may be neither disconnected nor connected. +**/ +@property (atomic, readonly) BOOL isDisconnected; +@property (atomic, readonly) BOOL isConnected; + +/** + * Returns the local or remote host and port to which this socket is connected, or nil and 0 if not connected. + * The host will be an IP address. +**/ +@property (atomic, readonly, nullable) NSString *connectedHost; +@property (atomic, readonly) uint16_t connectedPort; +@property (atomic, readonly, nullable) NSURL *connectedUrl; + +@property (atomic, readonly, nullable) NSString *localHost; +@property (atomic, readonly) uint16_t localPort; + +/** + * Returns the local or remote address to which this socket is connected, + * specified as a sockaddr structure wrapped in a NSData object. + * + * @seealso connectedHost + * @seealso connectedPort + * @seealso localHost + * @seealso localPort +**/ +@property (atomic, readonly, nullable) NSData *connectedAddress; +@property (atomic, readonly, nullable) NSData *localAddress; + +/** + * Returns whether the socket is IPv4 or IPv6. + * An accepting socket may be both. +**/ +@property (atomic, readonly) BOOL isIPv4; +@property (atomic, readonly) BOOL isIPv6; + +/** + * Returns whether or not the socket has been secured via SSL/TLS. + * + * See also the startTLS method. +**/ +@property (atomic, readonly) BOOL isSecure; + +#pragma mark Reading + +// The readData and writeData methods won't block (they are asynchronous). +// +// When a read is complete the socket:didReadData:withTag: delegate method is dispatched on the delegateQueue. +// When a write is complete the socket:didWriteDataWithTag: delegate method is dispatched on the delegateQueue. +// +// You may optionally set a timeout for any read/write operation. (To not timeout, use a negative time interval.) +// If a read/write opertion times out, the corresponding "socket:shouldTimeout..." delegate method +// is called to optionally allow you to extend the timeout. +// Upon a timeout, the "socket:didDisconnectWithError:" method is called +// +// The tag is for your convenience. +// You can use it as an array index, step number, state id, pointer, etc. + +/** + * Reads the first available bytes that become available on the socket. + * + * If the timeout value is negative, the read operation will not use a timeout. +**/ +- (void)readDataWithTimeout:(NSTimeInterval)timeout tag:(long)tag; + +/** + * Reads the first available bytes that become available on the socket. + * The bytes will be appended to the given byte buffer starting at the given offset. + * The given buffer will automatically be increased in size if needed. + * + * If the timeout value is negative, the read operation will not use a timeout. + * If the buffer if nil, the socket will create a buffer for you. + * + * If the bufferOffset is greater than the length of the given buffer, + * the method will do nothing, and the delegate will not be called. + * + * If you pass a buffer, you must not alter it in any way while the socket is using it. + * After completion, the data returned in socket:didReadData:withTag: will be a subset of the given buffer. + * That is, it will reference the bytes that were appended to the given buffer via + * the method [NSData dataWithBytesNoCopy:length:freeWhenDone:NO]. +**/ +- (void)readDataWithTimeout:(NSTimeInterval)timeout + buffer:(nullable NSMutableData *)buffer + bufferOffset:(NSUInteger)offset + tag:(long)tag; + +/** + * Reads the first available bytes that become available on the socket. + * The bytes will be appended to the given byte buffer starting at the given offset. + * The given buffer will automatically be increased in size if needed. + * A maximum of length bytes will be read. + * + * If the timeout value is negative, the read operation will not use a timeout. + * If the buffer if nil, a buffer will automatically be created for you. + * If maxLength is zero, no length restriction is enforced. + * + * If the bufferOffset is greater than the length of the given buffer, + * the method will do nothing, and the delegate will not be called. + * + * If you pass a buffer, you must not alter it in any way while the socket is using it. + * After completion, the data returned in socket:didReadData:withTag: will be a subset of the given buffer. + * That is, it will reference the bytes that were appended to the given buffer via + * the method [NSData dataWithBytesNoCopy:length:freeWhenDone:NO]. +**/ +- (void)readDataWithTimeout:(NSTimeInterval)timeout + buffer:(nullable NSMutableData *)buffer + bufferOffset:(NSUInteger)offset + maxLength:(NSUInteger)length + tag:(long)tag; + +/** + * Reads the given number of bytes. + * + * If the timeout value is negative, the read operation will not use a timeout. + * + * If the length is 0, this method does nothing and the delegate is not called. +**/ +- (void)readDataToLength:(NSUInteger)length withTimeout:(NSTimeInterval)timeout tag:(long)tag; + +/** + * Reads the given number of bytes. + * The bytes will be appended to the given byte buffer starting at the given offset. + * The given buffer will automatically be increased in size if needed. + * + * If the timeout value is negative, the read operation will not use a timeout. + * If the buffer if nil, a buffer will automatically be created for you. + * + * If the length is 0, this method does nothing and the delegate is not called. + * If the bufferOffset is greater than the length of the given buffer, + * the method will do nothing, and the delegate will not be called. + * + * If you pass a buffer, you must not alter it in any way while AsyncSocket is using it. + * After completion, the data returned in socket:didReadData:withTag: will be a subset of the given buffer. + * That is, it will reference the bytes that were appended to the given buffer via + * the method [NSData dataWithBytesNoCopy:length:freeWhenDone:NO]. +**/ +- (void)readDataToLength:(NSUInteger)length + withTimeout:(NSTimeInterval)timeout + buffer:(nullable NSMutableData *)buffer + bufferOffset:(NSUInteger)offset + tag:(long)tag; + +/** + * Reads bytes until (and including) the passed "data" parameter, which acts as a separator. + * + * If the timeout value is negative, the read operation will not use a timeout. + * + * If you pass nil or zero-length data as the "data" parameter, + * the method will do nothing (except maybe print a warning), and the delegate will not be called. + * + * To read a line from the socket, use the line separator (e.g. CRLF for HTTP, see below) as the "data" parameter. + * If you're developing your own custom protocol, be sure your separator can not occur naturally as + * part of the data between separators. + * For example, imagine you want to send several small documents over a socket. + * Using CRLF as a separator is likely unwise, as a CRLF could easily exist within the documents. + * In this particular example, it would be better to use a protocol similar to HTTP with + * a header that includes the length of the document. + * Also be careful that your separator cannot occur naturally as part of the encoding for a character. + * + * The given data (separator) parameter should be immutable. + * For performance reasons, the socket will retain it, not copy it. + * So if it is immutable, don't modify it while the socket is using it. +**/ +- (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag; + +/** + * Reads bytes until (and including) the passed "data" parameter, which acts as a separator. + * The bytes will be appended to the given byte buffer starting at the given offset. + * The given buffer will automatically be increased in size if needed. + * + * If the timeout value is negative, the read operation will not use a timeout. + * If the buffer if nil, a buffer will automatically be created for you. + * + * If the bufferOffset is greater than the length of the given buffer, + * the method will do nothing (except maybe print a warning), and the delegate will not be called. + * + * If you pass a buffer, you must not alter it in any way while the socket is using it. + * After completion, the data returned in socket:didReadData:withTag: will be a subset of the given buffer. + * That is, it will reference the bytes that were appended to the given buffer via + * the method [NSData dataWithBytesNoCopy:length:freeWhenDone:NO]. + * + * To read a line from the socket, use the line separator (e.g. CRLF for HTTP, see below) as the "data" parameter. + * If you're developing your own custom protocol, be sure your separator can not occur naturally as + * part of the data between separators. + * For example, imagine you want to send several small documents over a socket. + * Using CRLF as a separator is likely unwise, as a CRLF could easily exist within the documents. + * In this particular example, it would be better to use a protocol similar to HTTP with + * a header that includes the length of the document. + * Also be careful that your separator cannot occur naturally as part of the encoding for a character. + * + * The given data (separator) parameter should be immutable. + * For performance reasons, the socket will retain it, not copy it. + * So if it is immutable, don't modify it while the socket is using it. +**/ +- (void)readDataToData:(NSData *)data + withTimeout:(NSTimeInterval)timeout + buffer:(nullable NSMutableData *)buffer + bufferOffset:(NSUInteger)offset + tag:(long)tag; + +/** + * Reads bytes until (and including) the passed "data" parameter, which acts as a separator. + * + * If the timeout value is negative, the read operation will not use a timeout. + * + * If maxLength is zero, no length restriction is enforced. + * Otherwise if maxLength bytes are read without completing the read, + * it is treated similarly to a timeout - the socket is closed with a GCDAsyncSocketReadMaxedOutError. + * The read will complete successfully if exactly maxLength bytes are read and the given data is found at the end. + * + * If you pass nil or zero-length data as the "data" parameter, + * the method will do nothing (except maybe print a warning), and the delegate will not be called. + * If you pass a maxLength parameter that is less than the length of the data parameter, + * the method will do nothing (except maybe print a warning), and the delegate will not be called. + * + * To read a line from the socket, use the line separator (e.g. CRLF for HTTP, see below) as the "data" parameter. + * If you're developing your own custom protocol, be sure your separator can not occur naturally as + * part of the data between separators. + * For example, imagine you want to send several small documents over a socket. + * Using CRLF as a separator is likely unwise, as a CRLF could easily exist within the documents. + * In this particular example, it would be better to use a protocol similar to HTTP with + * a header that includes the length of the document. + * Also be careful that your separator cannot occur naturally as part of the encoding for a character. + * + * The given data (separator) parameter should be immutable. + * For performance reasons, the socket will retain it, not copy it. + * So if it is immutable, don't modify it while the socket is using it. +**/ +- (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout maxLength:(NSUInteger)length tag:(long)tag; + +/** + * Reads bytes until (and including) the passed "data" parameter, which acts as a separator. + * The bytes will be appended to the given byte buffer starting at the given offset. + * The given buffer will automatically be increased in size if needed. + * + * If the timeout value is negative, the read operation will not use a timeout. + * If the buffer if nil, a buffer will automatically be created for you. + * + * If maxLength is zero, no length restriction is enforced. + * Otherwise if maxLength bytes are read without completing the read, + * it is treated similarly to a timeout - the socket is closed with a GCDAsyncSocketReadMaxedOutError. + * The read will complete successfully if exactly maxLength bytes are read and the given data is found at the end. + * + * If you pass a maxLength parameter that is less than the length of the data (separator) parameter, + * the method will do nothing (except maybe print a warning), and the delegate will not be called. + * If the bufferOffset is greater than the length of the given buffer, + * the method will do nothing (except maybe print a warning), and the delegate will not be called. + * + * If you pass a buffer, you must not alter it in any way while the socket is using it. + * After completion, the data returned in socket:didReadData:withTag: will be a subset of the given buffer. + * That is, it will reference the bytes that were appended to the given buffer via + * the method [NSData dataWithBytesNoCopy:length:freeWhenDone:NO]. + * + * To read a line from the socket, use the line separator (e.g. CRLF for HTTP, see below) as the "data" parameter. + * If you're developing your own custom protocol, be sure your separator can not occur naturally as + * part of the data between separators. + * For example, imagine you want to send several small documents over a socket. + * Using CRLF as a separator is likely unwise, as a CRLF could easily exist within the documents. + * In this particular example, it would be better to use a protocol similar to HTTP with + * a header that includes the length of the document. + * Also be careful that your separator cannot occur naturally as part of the encoding for a character. + * + * The given data (separator) parameter should be immutable. + * For performance reasons, the socket will retain it, not copy it. + * So if it is immutable, don't modify it while the socket is using it. +**/ +- (void)readDataToData:(NSData *)data + withTimeout:(NSTimeInterval)timeout + buffer:(nullable NSMutableData *)buffer + bufferOffset:(NSUInteger)offset + maxLength:(NSUInteger)length + tag:(long)tag; + +/** + * Returns progress of the current read, from 0.0 to 1.0, or NaN if no current read (use isnan() to check). + * The parameters "tag", "done" and "total" will be filled in if they aren't NULL. +**/ +- (float)progressOfReadReturningTag:(nullable long *)tagPtr bytesDone:(nullable NSUInteger *)donePtr total:(nullable NSUInteger *)totalPtr; + +#pragma mark Writing + +/** + * Writes data to the socket, and calls the delegate when finished. + * + * If you pass in nil or zero-length data, this method does nothing and the delegate will not be called. + * If the timeout value is negative, the write operation will not use a timeout. + * + * Thread-Safety Note: + * If the given data parameter is mutable (NSMutableData) then you MUST NOT alter the data while + * the socket is writing it. In other words, it's not safe to alter the data until after the delegate method + * socket:didWriteDataWithTag: is invoked signifying that this particular write operation has completed. + * This is due to the fact that GCDAsyncSocket does NOT copy the data. It simply retains it. + * This is for performance reasons. Often times, if NSMutableData is passed, it is because + * a request/response was built up in memory. Copying this data adds an unwanted/unneeded overhead. + * If you need to write data from an immutable buffer, and you need to alter the buffer before the socket + * completes writing the bytes (which is NOT immediately after this method returns, but rather at a later time + * when the delegate method notifies you), then you should first copy the bytes, and pass the copy to this method. +**/ +- (void)writeData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag; + +/** + * Returns progress of the current write, from 0.0 to 1.0, or NaN if no current write (use isnan() to check). + * The parameters "tag", "done" and "total" will be filled in if they aren't NULL. +**/ +- (float)progressOfWriteReturningTag:(nullable long *)tagPtr bytesDone:(nullable NSUInteger *)donePtr total:(nullable NSUInteger *)totalPtr; + +#pragma mark Security + +/** + * Secures the connection using SSL/TLS. + * + * This method may be called at any time, and the TLS handshake will occur after all pending reads and writes + * are finished. This allows one the option of sending a protocol dependent StartTLS message, and queuing + * the upgrade to TLS at the same time, without having to wait for the write to finish. + * Any reads or writes scheduled after this method is called will occur over the secured connection. + * + * ==== The available TOP-LEVEL KEYS are: + * + * - GCDAsyncSocketManuallyEvaluateTrust + * The value must be of type NSNumber, encapsulating a BOOL value. + * If you set this to YES, then the underlying SecureTransport system will not evaluate the SecTrustRef of the peer. + * Instead it will pause at the moment evaulation would typically occur, + * and allow us to handle the security evaluation however we see fit. + * So GCDAsyncSocket will invoke the delegate method socket:shouldTrustPeer: passing the SecTrustRef. + * + * Note that if you set this option, then all other configuration keys are ignored. + * Evaluation will be completely up to you during the socket:didReceiveTrust:completionHandler: delegate method. + * + * For more information on trust evaluation see: + * Apple's Technical Note TN2232 - HTTPS Server Trust Evaluation + * https://developer.apple.com/library/ios/technotes/tn2232/_index.html + * + * If unspecified, the default value is NO. + * + * - GCDAsyncSocketUseCFStreamForTLS (iOS only) + * The value must be of type NSNumber, encapsulating a BOOL value. + * By default GCDAsyncSocket will use the SecureTransport layer to perform encryption. + * This gives us more control over the security protocol (many more configuration options), + * plus it allows us to optimize things like sys calls and buffer allocation. + * + * However, if you absolutely must, you can instruct GCDAsyncSocket to use the old-fashioned encryption + * technique by going through the CFStream instead. So instead of using SecureTransport, GCDAsyncSocket + * will instead setup a CFRead/CFWriteStream. And then set the kCFStreamPropertySSLSettings property + * (via CFReadStreamSetProperty / CFWriteStreamSetProperty) and will pass the given options to this method. + * + * Thus all the other keys in the given dictionary will be ignored by GCDAsyncSocket, + * and will passed directly CFReadStreamSetProperty / CFWriteStreamSetProperty. + * For more infomation on these keys, please see the documentation for kCFStreamPropertySSLSettings. + * + * If unspecified, the default value is NO. + * + * ==== The available CONFIGURATION KEYS are: + * + * - kCFStreamSSLPeerName + * The value must be of type NSString. + * It should match the name in the X.509 certificate given by the remote party. + * See Apple's documentation for SSLSetPeerDomainName. + * + * - kCFStreamSSLCertificates + * The value must be of type NSArray. + * See Apple's documentation for SSLSetCertificate. + * + * - kCFStreamSSLIsServer + * The value must be of type NSNumber, encapsulationg a BOOL value. + * See Apple's documentation for SSLCreateContext for iOS. + * This is optional for iOS. If not supplied, a NO value is the default. + * This is not needed for Mac OS X, and the value is ignored. + * + * - GCDAsyncSocketSSLPeerID + * The value must be of type NSData. + * You must set this value if you want to use TLS session resumption. + * See Apple's documentation for SSLSetPeerID. + * + * - GCDAsyncSocketSSLProtocolVersionMin + * - GCDAsyncSocketSSLProtocolVersionMax + * The value(s) must be of type NSNumber, encapsulting a SSLProtocol value. + * See Apple's documentation for SSLSetProtocolVersionMin & SSLSetProtocolVersionMax. + * See also the SSLProtocol typedef. + * + * - GCDAsyncSocketSSLSessionOptionFalseStart + * The value must be of type NSNumber, encapsulating a BOOL value. + * See Apple's documentation for kSSLSessionOptionFalseStart. + * + * - GCDAsyncSocketSSLSessionOptionSendOneByteRecord + * The value must be of type NSNumber, encapsulating a BOOL value. + * See Apple's documentation for kSSLSessionOptionSendOneByteRecord. + * + * - GCDAsyncSocketSSLCipherSuites + * The values must be of type NSArray. + * Each item within the array must be a NSNumber, encapsulating + * See Apple's documentation for SSLSetEnabledCiphers. + * See also the SSLCipherSuite typedef. + * + * - GCDAsyncSocketSSLDiffieHellmanParameters (Mac OS X only) + * The value must be of type NSData. + * See Apple's documentation for SSLSetDiffieHellmanParams. + * + * ==== The following UNAVAILABLE KEYS are: (with throw an exception) + * + * - kCFStreamSSLAllowsAnyRoot (UNAVAILABLE) + * You MUST use manual trust evaluation instead (see GCDAsyncSocketManuallyEvaluateTrust). + * Corresponding deprecated method: SSLSetAllowsAnyRoot + * + * - kCFStreamSSLAllowsExpiredRoots (UNAVAILABLE) + * You MUST use manual trust evaluation instead (see GCDAsyncSocketManuallyEvaluateTrust). + * Corresponding deprecated method: SSLSetAllowsExpiredRoots + * + * - kCFStreamSSLAllowsExpiredCertificates (UNAVAILABLE) + * You MUST use manual trust evaluation instead (see GCDAsyncSocketManuallyEvaluateTrust). + * Corresponding deprecated method: SSLSetAllowsExpiredCerts + * + * - kCFStreamSSLValidatesCertificateChain (UNAVAILABLE) + * You MUST use manual trust evaluation instead (see GCDAsyncSocketManuallyEvaluateTrust). + * Corresponding deprecated method: SSLSetEnableCertVerify + * + * - kCFStreamSSLLevel (UNAVAILABLE) + * You MUST use GCDAsyncSocketSSLProtocolVersionMin & GCDAsyncSocketSSLProtocolVersionMin instead. + * Corresponding deprecated method: SSLSetProtocolVersionEnabled + * + * + * Please refer to Apple's documentation for corresponding SSLFunctions. + * + * If you pass in nil or an empty dictionary, the default settings will be used. + * + * IMPORTANT SECURITY NOTE: + * The default settings will check to make sure the remote party's certificate is signed by a + * trusted 3rd party certificate agency (e.g. verisign) and that the certificate is not expired. + * However it will not verify the name on the certificate unless you + * give it a name to verify against via the kCFStreamSSLPeerName key. + * The security implications of this are important to understand. + * Imagine you are attempting to create a secure connection to MySecureServer.com, + * but your socket gets directed to MaliciousServer.com because of a hacked DNS server. + * If you simply use the default settings, and MaliciousServer.com has a valid certificate, + * the default settings will not detect any problems since the certificate is valid. + * To properly secure your connection in this particular scenario you + * should set the kCFStreamSSLPeerName property to "MySecureServer.com". + * + * You can also perform additional validation in socketDidSecure. +**/ +- (void)startTLS:(nullable NSDictionary *)tlsSettings; + +#pragma mark Advanced + +/** + * Traditionally sockets are not closed until the conversation is over. + * However, it is technically possible for the remote enpoint to close its write stream. + * Our socket would then be notified that there is no more data to be read, + * but our socket would still be writeable and the remote endpoint could continue to receive our data. + * + * The argument for this confusing functionality stems from the idea that a client could shut down its + * write stream after sending a request to the server, thus notifying the server there are to be no further requests. + * In practice, however, this technique did little to help server developers. + * + * To make matters worse, from a TCP perspective there is no way to tell the difference from a read stream close + * and a full socket close. They both result in the TCP stack receiving a FIN packet. The only way to tell + * is by continuing to write to the socket. If it was only a read stream close, then writes will continue to work. + * Otherwise an error will be occur shortly (when the remote end sends us a RST packet). + * + * In addition to the technical challenges and confusion, many high level socket/stream API's provide + * no support for dealing with the problem. If the read stream is closed, the API immediately declares the + * socket to be closed, and shuts down the write stream as well. In fact, this is what Apple's CFStream API does. + * It might sound like poor design at first, but in fact it simplifies development. + * + * The vast majority of the time if the read stream is closed it's because the remote endpoint closed its socket. + * Thus it actually makes sense to close the socket at this point. + * And in fact this is what most networking developers want and expect to happen. + * However, if you are writing a server that interacts with a plethora of clients, + * you might encounter a client that uses the discouraged technique of shutting down its write stream. + * If this is the case, you can set this property to NO, + * and make use of the socketDidCloseReadStream delegate method. + * + * The default value is YES. +**/ +@property (atomic, assign, readwrite) BOOL autoDisconnectOnClosedReadStream; + +/** + * GCDAsyncSocket maintains thread safety by using an internal serial dispatch_queue. + * In most cases, the instance creates this queue itself. + * However, to allow for maximum flexibility, the internal queue may be passed in the init method. + * This allows for some advanced options such as controlling socket priority via target queues. + * However, when one begins to use target queues like this, they open the door to some specific deadlock issues. + * + * For example, imagine there are 2 queues: + * dispatch_queue_t socketQueue; + * dispatch_queue_t socketTargetQueue; + * + * If you do this (pseudo-code): + * socketQueue.targetQueue = socketTargetQueue; + * + * Then all socketQueue operations will actually get run on the given socketTargetQueue. + * This is fine and works great in most situations. + * But if you run code directly from within the socketTargetQueue that accesses the socket, + * you could potentially get deadlock. Imagine the following code: + * + * - (BOOL)socketHasSomething + * { + * __block BOOL result = NO; + * dispatch_block_t block = ^{ + * result = [self someInternalMethodToBeRunOnlyOnSocketQueue]; + * } + * if (is_executing_on_queue(socketQueue)) + * block(); + * else + * dispatch_sync(socketQueue, block); + * + * return result; + * } + * + * What happens if you call this method from the socketTargetQueue? The result is deadlock. + * This is because the GCD API offers no mechanism to discover a queue's targetQueue. + * Thus we have no idea if our socketQueue is configured with a targetQueue. + * If we had this information, we could easily avoid deadlock. + * But, since these API's are missing or unfeasible, you'll have to explicitly set it. + * + * IF you pass a socketQueue via the init method, + * AND you've configured the passed socketQueue with a targetQueue, + * THEN you should pass the end queue in the target hierarchy. + * + * For example, consider the following queue hierarchy: + * socketQueue -> ipQueue -> moduleQueue + * + * This example demonstrates priority shaping within some server. + * All incoming client connections from the same IP address are executed on the same target queue. + * And all connections for a particular module are executed on the same target queue. + * Thus, the priority of all networking for the entire module can be changed on the fly. + * Additionally, networking traffic from a single IP cannot monopolize the module. + * + * Here's how you would accomplish something like that: + * - (dispatch_queue_t)newSocketQueueForConnectionFromAddress:(NSData *)address onSocket:(GCDAsyncSocket *)sock + * { + * dispatch_queue_t socketQueue = dispatch_queue_create("", NULL); + * dispatch_queue_t ipQueue = [self ipQueueForAddress:address]; + * + * dispatch_set_target_queue(socketQueue, ipQueue); + * dispatch_set_target_queue(iqQueue, moduleQueue); + * + * return socketQueue; + * } + * - (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket + * { + * [clientConnections addObject:newSocket]; + * [newSocket markSocketQueueTargetQueue:moduleQueue]; + * } + * + * Note: This workaround is ONLY needed if you intend to execute code directly on the ipQueue or moduleQueue. + * This is often NOT the case, as such queues are used solely for execution shaping. +**/ +- (void)markSocketQueueTargetQueue:(dispatch_queue_t)socketQueuesPreConfiguredTargetQueue; +- (void)unmarkSocketQueueTargetQueue:(dispatch_queue_t)socketQueuesPreviouslyConfiguredTargetQueue; + +/** + * It's not thread-safe to access certain variables from outside the socket's internal queue. + * + * For example, the socket file descriptor. + * File descriptors are simply integers which reference an index in the per-process file table. + * However, when one requests a new file descriptor (by opening a file or socket), + * the file descriptor returned is guaranteed to be the lowest numbered unused descriptor. + * So if we're not careful, the following could be possible: + * + * - Thread A invokes a method which returns the socket's file descriptor. + * - The socket is closed via the socket's internal queue on thread B. + * - Thread C opens a file, and subsequently receives the file descriptor that was previously the socket's FD. + * - Thread A is now accessing/altering the file instead of the socket. + * + * In addition to this, other variables are not actually objects, + * and thus cannot be retained/released or even autoreleased. + * An example is the sslContext, of type SSLContextRef, which is actually a malloc'd struct. + * + * Although there are internal variables that make it difficult to maintain thread-safety, + * it is important to provide access to these variables + * to ensure this class can be used in a wide array of environments. + * This method helps to accomplish this by invoking the current block on the socket's internal queue. + * The methods below can be invoked from within the block to access + * those generally thread-unsafe internal variables in a thread-safe manner. + * The given block will be invoked synchronously on the socket's internal queue. + * + * If you save references to any protected variables and use them outside the block, you do so at your own peril. +**/ +- (void)performBlock:(dispatch_block_t)block; + +/** + * These methods are only available from within the context of a performBlock: invocation. + * See the documentation for the performBlock: method above. + * + * Provides access to the socket's file descriptor(s). + * If the socket is a server socket (is accepting incoming connections), + * it might actually have multiple internal socket file descriptors - one for IPv4 and one for IPv6. +**/ +- (int)socketFD; +- (int)socket4FD; +- (int)socket6FD; + +#if TARGET_OS_IPHONE + +/** + * These methods are only available from within the context of a performBlock: invocation. + * See the documentation for the performBlock: method above. + * + * Provides access to the socket's internal CFReadStream/CFWriteStream. + * + * These streams are only used as workarounds for specific iOS shortcomings: + * + * - Apple has decided to keep the SecureTransport framework private is iOS. + * This means the only supplied way to do SSL/TLS is via CFStream or some other API layered on top of it. + * Thus, in order to provide SSL/TLS support on iOS we are forced to rely on CFStream, + * instead of the preferred and faster and more powerful SecureTransport. + * + * - If a socket doesn't have backgrounding enabled, and that socket is closed while the app is backgrounded, + * Apple only bothers to notify us via the CFStream API. + * The faster and more powerful GCD API isn't notified properly in this case. + * + * See also: (BOOL)enableBackgroundingOnSocket +**/ +- (nullable CFReadStreamRef)readStream; +- (nullable CFWriteStreamRef)writeStream; + +/** + * This method is only available from within the context of a performBlock: invocation. + * See the documentation for the performBlock: method above. + * + * Configures the socket to allow it to operate when the iOS application has been backgrounded. + * In other words, this method creates a read & write stream, and invokes: + * + * CFReadStreamSetProperty(readStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); + * CFWriteStreamSetProperty(writeStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); + * + * Returns YES if successful, NO otherwise. + * + * Note: Apple does not officially support backgrounding server sockets. + * That is, if your socket is accepting incoming connections, Apple does not officially support + * allowing iOS applications to accept incoming connections while an app is backgrounded. + * + * Example usage: + * + * - (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port + * { + * [asyncSocket performBlock:^{ + * [asyncSocket enableBackgroundingOnSocket]; + * }]; + * } +**/ +- (BOOL)enableBackgroundingOnSocket; + +#endif + +/** + * This method is only available from within the context of a performBlock: invocation. + * See the documentation for the performBlock: method above. + * + * Provides access to the socket's SSLContext, if SSL/TLS has been started on the socket. +**/ +- (nullable SSLContextRef)sslContext; + +#pragma mark Utilities + +/** + * The address lookup utility used by the class. + * This method is synchronous, so it's recommended you use it on a background thread/queue. + * + * The special strings "localhost" and "loopback" return the loopback address for IPv4 and IPv6. + * + * @returns + * A mutable array with all IPv4 and IPv6 addresses returned by getaddrinfo. + * The addresses are specifically for TCP connections. + * You can filter the addresses, if needed, using the other utility methods provided by the class. +**/ ++ (nullable NSMutableArray *)lookupHost:(NSString *)host port:(uint16_t)port error:(NSError **)errPtr; + +/** + * Extracting host and port information from raw address data. +**/ + ++ (nullable NSString *)hostFromAddress:(NSData *)address; ++ (uint16_t)portFromAddress:(NSData *)address; + ++ (BOOL)isIPv4Address:(NSData *)address; ++ (BOOL)isIPv6Address:(NSData *)address; + ++ (BOOL)getHost:( NSString * __nullable * __nullable)hostPtr port:(nullable uint16_t *)portPtr fromAddress:(NSData *)address; + ++ (BOOL)getHost:(NSString * __nullable * __nullable)hostPtr port:(nullable uint16_t *)portPtr family:(nullable sa_family_t *)afPtr fromAddress:(NSData *)address; + +/** + * A few common line separators, for use with the readDataToData:... methods. +**/ ++ (NSData *)CRLFData; // 0x0D0A ++ (NSData *)CRData; // 0x0D ++ (NSData *)LFData; // 0x0A ++ (NSData *)ZeroData; // 0x00 + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@protocol GCDAsyncSocketDelegate +@optional + +/** + * This method is called immediately prior to socket:didAcceptNewSocket:. + * It optionally allows a listening socket to specify the socketQueue for a new accepted socket. + * If this method is not implemented, or returns NULL, the new accepted socket will create its own default queue. + * + * Since you cannot autorelease a dispatch_queue, + * this method uses the "new" prefix in its name to specify that the returned queue has been retained. + * + * Thus you could do something like this in the implementation: + * return dispatch_queue_create("MyQueue", NULL); + * + * If you are placing multiple sockets on the same queue, + * then care should be taken to increment the retain count each time this method is invoked. + * + * For example, your implementation might look something like this: + * dispatch_retain(myExistingQueue); + * return myExistingQueue; +**/ +- (nullable dispatch_queue_t)newSocketQueueForConnectionFromAddress:(NSData *)address onSocket:(GCDAsyncSocket *)sock; + +/** + * Called when a socket accepts a connection. + * Another socket is automatically spawned to handle it. + * + * You must retain the newSocket if you wish to handle the connection. + * Otherwise the newSocket instance will be released and the spawned connection will be closed. + * + * By default the new socket will have the same delegate and delegateQueue. + * You may, of course, change this at any time. +**/ +- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket; + +/** + * Called when a socket connects and is ready for reading and writing. + * The host parameter will be an IP address, not a DNS name. +**/ +- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port; + +/** + * Called when a socket connects and is ready for reading and writing. + * The host parameter will be an IP address, not a DNS name. + **/ +- (void)socket:(GCDAsyncSocket *)sock didConnectToUrl:(NSURL *)url; + +/** + * Called when a socket has completed reading the requested data into memory. + * Not called if there is an error. +**/ +- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag; + +/** + * Called when a socket has read in data, but has not yet completed the read. + * This would occur if using readToData: or readToLength: methods. + * It may be used to for things such as updating progress bars. +**/ +- (void)socket:(GCDAsyncSocket *)sock didReadPartialDataOfLength:(NSUInteger)partialLength tag:(long)tag; + +/** + * Called when a socket has completed writing the requested data. Not called if there is an error. +**/ +- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag; + +/** + * Called when a socket has written some data, but has not yet completed the entire write. + * It may be used to for things such as updating progress bars. +**/ +- (void)socket:(GCDAsyncSocket *)sock didWritePartialDataOfLength:(NSUInteger)partialLength tag:(long)tag; + +/** + * Called if a read operation has reached its timeout without completing. + * This method allows you to optionally extend the timeout. + * If you return a positive time interval (> 0) the read's timeout will be extended by the given amount. + * If you don't implement this method, or return a non-positive time interval (<= 0) the read will timeout as usual. + * + * The elapsed parameter is the sum of the original timeout, plus any additions previously added via this method. + * The length parameter is the number of bytes that have been read so far for the read operation. + * + * Note that this method may be called multiple times for a single read if you return positive numbers. +**/ +- (NSTimeInterval)socket:(GCDAsyncSocket *)sock shouldTimeoutReadWithTag:(long)tag + elapsed:(NSTimeInterval)elapsed + bytesDone:(NSUInteger)length; + +/** + * Called if a write operation has reached its timeout without completing. + * This method allows you to optionally extend the timeout. + * If you return a positive time interval (> 0) the write's timeout will be extended by the given amount. + * If you don't implement this method, or return a non-positive time interval (<= 0) the write will timeout as usual. + * + * The elapsed parameter is the sum of the original timeout, plus any additions previously added via this method. + * The length parameter is the number of bytes that have been written so far for the write operation. + * + * Note that this method may be called multiple times for a single write if you return positive numbers. +**/ +- (NSTimeInterval)socket:(GCDAsyncSocket *)sock shouldTimeoutWriteWithTag:(long)tag + elapsed:(NSTimeInterval)elapsed + bytesDone:(NSUInteger)length; + +/** + * Conditionally called if the read stream closes, but the write stream may still be writeable. + * + * This delegate method is only called if autoDisconnectOnClosedReadStream has been set to NO. + * See the discussion on the autoDisconnectOnClosedReadStream method for more information. +**/ +- (void)socketDidCloseReadStream:(GCDAsyncSocket *)sock; + +/** + * Called when a socket disconnects with or without error. + * + * If you call the disconnect method, and the socket wasn't already disconnected, + * then an invocation of this delegate method will be enqueued on the delegateQueue + * before the disconnect method returns. + * + * Note: If the GCDAsyncSocket instance is deallocated while it is still connected, + * and the delegate is not also deallocated, then this method will be invoked, + * but the sock parameter will be nil. (It must necessarily be nil since it is no longer available.) + * This is a generally rare, but is possible if one writes code like this: + * + * asyncSocket = nil; // I'm implicitly disconnecting the socket + * + * In this case it may preferrable to nil the delegate beforehand, like this: + * + * asyncSocket.delegate = nil; // Don't invoke my delegate method + * asyncSocket = nil; // I'm implicitly disconnecting the socket + * + * Of course, this depends on how your state machine is configured. +**/ +- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(nullable NSError *)err; + +/** + * Called after the socket has successfully completed SSL/TLS negotiation. + * This method is not called unless you use the provided startTLS method. + * + * If a SSL/TLS negotiation fails (invalid certificate, etc) then the socket will immediately close, + * and the socketDidDisconnect:withError: delegate method will be called with the specific SSL error code. +**/ +- (void)socketDidSecure:(GCDAsyncSocket *)sock; + +/** + * Allows a socket delegate to hook into the TLS handshake and manually validate the peer it's connecting to. + * + * This is only called if startTLS is invoked with options that include: + * - GCDAsyncSocketManuallyEvaluateTrust == YES + * + * Typically the delegate will use SecTrustEvaluate (and related functions) to properly validate the peer. + * + * Note from Apple's documentation: + * Because [SecTrustEvaluate] might look on the network for certificates in the certificate chain, + * [it] might block while attempting network access. You should never call it from your main thread; + * call it only from within a function running on a dispatch queue or on a separate thread. + * + * Thus this method uses a completionHandler block rather than a normal return value. + * The completionHandler block is thread-safe, and may be invoked from a background queue/thread. + * It is safe to invoke the completionHandler block even if the socket has been closed. +**/ +- (void)socket:(GCDAsyncSocket *)sock didReceiveTrust:(SecTrustRef)trust + completionHandler:(void (^)(BOOL shouldTrustPeer))completionHandler; + +@end +NS_ASSUME_NONNULL_END diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/GCDAsyncSocket.m b/actor-sdk/sdk-core-ios/ActorSDK/Sources/GCDAsyncSocket.m new file mode 100755 index 0000000000..3546d105b7 --- /dev/null +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/GCDAsyncSocket.m @@ -0,0 +1,8365 @@ +// +// GCDAsyncSocket.m +// +// This class is in the public domain. +// Originally created by Robbie Hanson in Q4 2010. +// Updated and maintained by Deusty LLC and the Apple development community. +// +// https://github.com/robbiehanson/CocoaAsyncSocket +// + +#import "GCDAsyncSocket.h" + +#if TARGET_OS_IPHONE +#import +#endif + +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import + +#if ! __has_feature(objc_arc) +#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). +// For more information see: https://github.com/robbiehanson/CocoaAsyncSocket/wiki/ARC +#endif + + +#ifndef GCDAsyncSocketLoggingEnabled +#define GCDAsyncSocketLoggingEnabled 0 +#endif + +#if GCDAsyncSocketLoggingEnabled + +// Logging Enabled - See log level below + +// Logging uses the CocoaLumberjack framework (which is also GCD based). +// https://github.com/robbiehanson/CocoaLumberjack +// +// It allows us to do a lot of logging without significantly slowing down the code. +#import "DDLog.h" + +#define LogAsync YES +#define LogContext GCDAsyncSocketLoggingContext + +#define LogObjc(flg, frmt, ...) LOG_OBJC_MAYBE(LogAsync, logLevel, flg, LogContext, frmt, ##__VA_ARGS__) +#define LogC(flg, frmt, ...) LOG_C_MAYBE(LogAsync, logLevel, flg, LogContext, frmt, ##__VA_ARGS__) + +#define LogError(frmt, ...) LogObjc(LOG_FLAG_ERROR, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) +#define LogWarn(frmt, ...) LogObjc(LOG_FLAG_WARN, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) +#define LogInfo(frmt, ...) LogObjc(LOG_FLAG_INFO, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) +#define LogVerbose(frmt, ...) LogObjc(LOG_FLAG_VERBOSE, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) + +#define LogCError(frmt, ...) LogC(LOG_FLAG_ERROR, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) +#define LogCWarn(frmt, ...) LogC(LOG_FLAG_WARN, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) +#define LogCInfo(frmt, ...) LogC(LOG_FLAG_INFO, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) +#define LogCVerbose(frmt, ...) LogC(LOG_FLAG_VERBOSE, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) + +#define LogTrace() LogObjc(LOG_FLAG_VERBOSE, @"%@: %@", THIS_FILE, THIS_METHOD) +#define LogCTrace() LogC(LOG_FLAG_VERBOSE, @"%@: %s", THIS_FILE, __FUNCTION__) + +#ifndef GCDAsyncSocketLogLevel +#define GCDAsyncSocketLogLevel LOG_LEVEL_VERBOSE +#endif + +// Log levels : off, error, warn, info, verbose +static const int logLevel = GCDAsyncSocketLogLevel; + +#else + +// Logging Disabled + +#define LogError(frmt, ...) {} +#define LogWarn(frmt, ...) {} +#define LogInfo(frmt, ...) {} +#define LogVerbose(frmt, ...) {} + +#define LogCError(frmt, ...) {} +#define LogCWarn(frmt, ...) {} +#define LogCInfo(frmt, ...) {} +#define LogCVerbose(frmt, ...) {} + +#define LogTrace() {} +#define LogCTrace(frmt, ...) {} + +#endif + +/** + * Seeing a return statements within an inner block + * can sometimes be mistaken for a return point of the enclosing method. + * This makes inline blocks a bit easier to read. +**/ +#define return_from_block return + +/** + * A socket file descriptor is really just an integer. + * It represents the index of the socket within the kernel. + * This makes invalid file descriptor comparisons easier to read. +**/ +#define SOCKET_NULL -1 + + +NSString *const GCDAsyncSocketException = @"GCDAsyncSocketException"; +NSString *const GCDAsyncSocketErrorDomain = @"GCDAsyncSocketErrorDomain"; + +NSString *const GCDAsyncSocketQueueName = @"GCDAsyncSocket"; +NSString *const GCDAsyncSocketThreadName = @"GCDAsyncSocket-CFStream"; + +NSString *const GCDAsyncSocketManuallyEvaluateTrust = @"GCDAsyncSocketManuallyEvaluateTrust"; +#if TARGET_OS_IPHONE +NSString *const GCDAsyncSocketUseCFStreamForTLS = @"GCDAsyncSocketUseCFStreamForTLS"; +#endif +NSString *const GCDAsyncSocketSSLPeerID = @"GCDAsyncSocketSSLPeerID"; +NSString *const GCDAsyncSocketSSLProtocolVersionMin = @"GCDAsyncSocketSSLProtocolVersionMin"; +NSString *const GCDAsyncSocketSSLProtocolVersionMax = @"GCDAsyncSocketSSLProtocolVersionMax"; +NSString *const GCDAsyncSocketSSLSessionOptionFalseStart = @"GCDAsyncSocketSSLSessionOptionFalseStart"; +NSString *const GCDAsyncSocketSSLSessionOptionSendOneByteRecord = @"GCDAsyncSocketSSLSessionOptionSendOneByteRecord"; +NSString *const GCDAsyncSocketSSLCipherSuites = @"GCDAsyncSocketSSLCipherSuites"; +#if !TARGET_OS_IPHONE +NSString *const GCDAsyncSocketSSLDiffieHellmanParameters = @"GCDAsyncSocketSSLDiffieHellmanParameters"; +#endif + +enum GCDAsyncSocketFlags +{ + kSocketStarted = 1 << 0, // If set, socket has been started (accepting/connecting) + kConnected = 1 << 1, // If set, the socket is connected + kForbidReadsWrites = 1 << 2, // If set, no new reads or writes are allowed + kReadsPaused = 1 << 3, // If set, reads are paused due to possible timeout + kWritesPaused = 1 << 4, // If set, writes are paused due to possible timeout + kDisconnectAfterReads = 1 << 5, // If set, disconnect after no more reads are queued + kDisconnectAfterWrites = 1 << 6, // If set, disconnect after no more writes are queued + kSocketCanAcceptBytes = 1 << 7, // If set, we know socket can accept bytes. If unset, it's unknown. + kReadSourceSuspended = 1 << 8, // If set, the read source is suspended + kWriteSourceSuspended = 1 << 9, // If set, the write source is suspended + kQueuedTLS = 1 << 10, // If set, we've queued an upgrade to TLS + kStartingReadTLS = 1 << 11, // If set, we're waiting for TLS negotiation to complete + kStartingWriteTLS = 1 << 12, // If set, we're waiting for TLS negotiation to complete + kSocketSecure = 1 << 13, // If set, socket is using secure communication via SSL/TLS + kSocketHasReadEOF = 1 << 14, // If set, we have read EOF from socket + kReadStreamClosed = 1 << 15, // If set, we've read EOF plus prebuffer has been drained + kDealloc = 1 << 16, // If set, the socket is being deallocated +#if TARGET_OS_IPHONE + kAddedStreamsToRunLoop = 1 << 17, // If set, CFStreams have been added to listener thread + kUsingCFStreamForTLS = 1 << 18, // If set, we're forced to use CFStream instead of SecureTransport + kSecureSocketHasBytesAvailable = 1 << 19, // If set, CFReadStream has notified us of bytes available +#endif +}; + +enum GCDAsyncSocketConfig +{ + kIPv4Disabled = 1 << 0, // If set, IPv4 is disabled + kIPv6Disabled = 1 << 1, // If set, IPv6 is disabled + kPreferIPv6 = 1 << 2, // If set, IPv6 is preferred over IPv4 + kAllowHalfDuplexConnection = 1 << 3, // If set, the socket will stay open even if the read stream closes +}; + +#if TARGET_OS_IPHONE + static NSThread *cfstreamThread; // Used for CFStreams + + + static uint64_t cfstreamThreadRetainCount; // setup & teardown + static dispatch_queue_t cfstreamThreadSetupQueue; // setup & teardown +#endif + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * A PreBuffer is used when there is more data available on the socket + * than is being requested by current read request. + * In this case we slurp up all data from the socket (to minimize sys calls), + * and store additional yet unread data in a "prebuffer". + * + * The prebuffer is entirely drained before we read from the socket again. + * In other words, a large chunk of data is written is written to the prebuffer. + * The prebuffer is then drained via a series of one or more reads (for subsequent read request(s)). + * + * A ring buffer was once used for this purpose. + * But a ring buffer takes up twice as much memory as needed (double the size for mirroring). + * In fact, it generally takes up more than twice the needed size as everything has to be rounded up to vm_page_size. + * And since the prebuffer is always completely drained after being written to, a full ring buffer isn't needed. + * + * The current design is very simple and straight-forward, while also keeping memory requirements lower. +**/ + +@interface GCDAsyncSocketPreBuffer : NSObject +{ + uint8_t *preBuffer; + size_t preBufferSize; + + uint8_t *readPointer; + uint8_t *writePointer; +} + +- (id)initWithCapacity:(size_t)numBytes; + +- (void)ensureCapacityForWrite:(size_t)numBytes; + +- (size_t)availableBytes; +- (uint8_t *)readBuffer; + +- (void)getReadBuffer:(uint8_t **)bufferPtr availableBytes:(size_t *)availableBytesPtr; + +- (size_t)availableSpace; +- (uint8_t *)writeBuffer; + +- (void)getWriteBuffer:(uint8_t **)bufferPtr availableSpace:(size_t *)availableSpacePtr; + +- (void)didRead:(size_t)bytesRead; +- (void)didWrite:(size_t)bytesWritten; + +- (void)reset; + +@end + +@implementation GCDAsyncSocketPreBuffer + +- (id)initWithCapacity:(size_t)numBytes +{ + if ((self = [super init])) + { + preBufferSize = numBytes; + preBuffer = malloc(preBufferSize); + + readPointer = preBuffer; + writePointer = preBuffer; + } + return self; +} + +- (void)dealloc +{ + if (preBuffer) + free(preBuffer); +} + +- (void)ensureCapacityForWrite:(size_t)numBytes +{ + size_t availableSpace = [self availableSpace]; + + if (numBytes > availableSpace) + { + size_t additionalBytes = numBytes - availableSpace; + + size_t newPreBufferSize = preBufferSize + additionalBytes; + uint8_t *newPreBuffer = realloc(preBuffer, newPreBufferSize); + + size_t readPointerOffset = readPointer - preBuffer; + size_t writePointerOffset = writePointer - preBuffer; + + preBuffer = newPreBuffer; + preBufferSize = newPreBufferSize; + + readPointer = preBuffer + readPointerOffset; + writePointer = preBuffer + writePointerOffset; + } +} + +- (size_t)availableBytes +{ + return writePointer - readPointer; +} + +- (uint8_t *)readBuffer +{ + return readPointer; +} + +- (void)getReadBuffer:(uint8_t **)bufferPtr availableBytes:(size_t *)availableBytesPtr +{ + if (bufferPtr) *bufferPtr = readPointer; + if (availableBytesPtr) *availableBytesPtr = [self availableBytes]; +} + +- (void)didRead:(size_t)bytesRead +{ + readPointer += bytesRead; + + if (readPointer == writePointer) + { + // The prebuffer has been drained. Reset pointers. + readPointer = preBuffer; + writePointer = preBuffer; + } +} + +- (size_t)availableSpace +{ + return preBufferSize - (writePointer - preBuffer); +} + +- (uint8_t *)writeBuffer +{ + return writePointer; +} + +- (void)getWriteBuffer:(uint8_t **)bufferPtr availableSpace:(size_t *)availableSpacePtr +{ + if (bufferPtr) *bufferPtr = writePointer; + if (availableSpacePtr) *availableSpacePtr = [self availableSpace]; +} + +- (void)didWrite:(size_t)bytesWritten +{ + writePointer += bytesWritten; +} + +- (void)reset +{ + readPointer = preBuffer; + writePointer = preBuffer; +} + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * The GCDAsyncReadPacket encompasses the instructions for any given read. + * The content of a read packet allows the code to determine if we're: + * - reading to a certain length + * - reading to a certain separator + * - or simply reading the first chunk of available data +**/ +@interface GCDAsyncReadPacket : NSObject +{ + @public + NSMutableData *buffer; + NSUInteger startOffset; + NSUInteger bytesDone; + NSUInteger maxLength; + NSTimeInterval timeout; + NSUInteger readLength; + NSData *term; + BOOL bufferOwner; + NSUInteger originalBufferLength; + long tag; +} +- (id)initWithData:(NSMutableData *)d + startOffset:(NSUInteger)s + maxLength:(NSUInteger)m + timeout:(NSTimeInterval)t + readLength:(NSUInteger)l + terminator:(NSData *)e + tag:(long)i; + +- (void)ensureCapacityForAdditionalDataOfLength:(NSUInteger)bytesToRead; + +- (NSUInteger)optimalReadLengthWithDefault:(NSUInteger)defaultValue shouldPreBuffer:(BOOL *)shouldPreBufferPtr; + +- (NSUInteger)readLengthForNonTermWithHint:(NSUInteger)bytesAvailable; +- (NSUInteger)readLengthForTermWithHint:(NSUInteger)bytesAvailable shouldPreBuffer:(BOOL *)shouldPreBufferPtr; +- (NSUInteger)readLengthForTermWithPreBuffer:(GCDAsyncSocketPreBuffer *)preBuffer found:(BOOL *)foundPtr; + +- (NSInteger)searchForTermAfterPreBuffering:(ssize_t)numBytes; + +@end + +@implementation GCDAsyncReadPacket + +- (id)initWithData:(NSMutableData *)d + startOffset:(NSUInteger)s + maxLength:(NSUInteger)m + timeout:(NSTimeInterval)t + readLength:(NSUInteger)l + terminator:(NSData *)e + tag:(long)i +{ + if((self = [super init])) + { + bytesDone = 0; + maxLength = m; + timeout = t; + readLength = l; + term = [e copy]; + tag = i; + + if (d) + { + buffer = d; + startOffset = s; + bufferOwner = NO; + originalBufferLength = [d length]; + } + else + { + if (readLength > 0) + buffer = [[NSMutableData alloc] initWithLength:readLength]; + else + buffer = [[NSMutableData alloc] initWithLength:0]; + + startOffset = 0; + bufferOwner = YES; + originalBufferLength = 0; + } + } + return self; +} + +/** + * Increases the length of the buffer (if needed) to ensure a read of the given size will fit. +**/ +- (void)ensureCapacityForAdditionalDataOfLength:(NSUInteger)bytesToRead +{ + NSUInteger buffSize = [buffer length]; + NSUInteger buffUsed = startOffset + bytesDone; + + NSUInteger buffSpace = buffSize - buffUsed; + + if (bytesToRead > buffSpace) + { + NSUInteger buffInc = bytesToRead - buffSpace; + + [buffer increaseLengthBy:buffInc]; + } +} + +/** + * This method is used when we do NOT know how much data is available to be read from the socket. + * This method returns the default value unless it exceeds the specified readLength or maxLength. + * + * Furthermore, the shouldPreBuffer decision is based upon the packet type, + * and whether the returned value would fit in the current buffer without requiring a resize of the buffer. +**/ +- (NSUInteger)optimalReadLengthWithDefault:(NSUInteger)defaultValue shouldPreBuffer:(BOOL *)shouldPreBufferPtr +{ + NSUInteger result; + + if (readLength > 0) + { + // Read a specific length of data + + result = MIN(defaultValue, (readLength - bytesDone)); + + // There is no need to prebuffer since we know exactly how much data we need to read. + // Even if the buffer isn't currently big enough to fit this amount of data, + // it would have to be resized eventually anyway. + + if (shouldPreBufferPtr) + *shouldPreBufferPtr = NO; + } + else + { + // Either reading until we find a specified terminator, + // or we're simply reading all available data. + // + // In other words, one of: + // + // - readDataToData packet + // - readDataWithTimeout packet + + if (maxLength > 0) + result = MIN(defaultValue, (maxLength - bytesDone)); + else + result = defaultValue; + + // Since we don't know the size of the read in advance, + // the shouldPreBuffer decision is based upon whether the returned value would fit + // in the current buffer without requiring a resize of the buffer. + // + // This is because, in all likelyhood, the amount read from the socket will be less than the default value. + // Thus we should avoid over-allocating the read buffer when we can simply use the pre-buffer instead. + + if (shouldPreBufferPtr) + { + NSUInteger buffSize = [buffer length]; + NSUInteger buffUsed = startOffset + bytesDone; + + NSUInteger buffSpace = buffSize - buffUsed; + + if (buffSpace >= result) + *shouldPreBufferPtr = NO; + else + *shouldPreBufferPtr = YES; + } + } + + return result; +} + +/** + * For read packets without a set terminator, returns the amount of data + * that can be read without exceeding the readLength or maxLength. + * + * The given parameter indicates the number of bytes estimated to be available on the socket, + * which is taken into consideration during the calculation. + * + * The given hint MUST be greater than zero. +**/ +- (NSUInteger)readLengthForNonTermWithHint:(NSUInteger)bytesAvailable +{ + NSAssert(term == nil, @"This method does not apply to term reads"); + NSAssert(bytesAvailable > 0, @"Invalid parameter: bytesAvailable"); + + if (readLength > 0) + { + // Read a specific length of data + + return MIN(bytesAvailable, (readLength - bytesDone)); + + // No need to avoid resizing the buffer. + // If the user provided their own buffer, + // and told us to read a certain length of data that exceeds the size of the buffer, + // then it is clear that our code will resize the buffer during the read operation. + // + // This method does not actually do any resizing. + // The resizing will happen elsewhere if needed. + } + else + { + // Read all available data + + NSUInteger result = bytesAvailable; + + if (maxLength > 0) + { + result = MIN(result, (maxLength - bytesDone)); + } + + // No need to avoid resizing the buffer. + // If the user provided their own buffer, + // and told us to read all available data without giving us a maxLength, + // then it is clear that our code might resize the buffer during the read operation. + // + // This method does not actually do any resizing. + // The resizing will happen elsewhere if needed. + + return result; + } +} + +/** + * For read packets with a set terminator, returns the amount of data + * that can be read without exceeding the maxLength. + * + * The given parameter indicates the number of bytes estimated to be available on the socket, + * which is taken into consideration during the calculation. + * + * To optimize memory allocations, mem copies, and mem moves + * the shouldPreBuffer boolean value will indicate if the data should be read into a prebuffer first, + * or if the data can be read directly into the read packet's buffer. +**/ +- (NSUInteger)readLengthForTermWithHint:(NSUInteger)bytesAvailable shouldPreBuffer:(BOOL *)shouldPreBufferPtr +{ + NSAssert(term != nil, @"This method does not apply to non-term reads"); + NSAssert(bytesAvailable > 0, @"Invalid parameter: bytesAvailable"); + + + NSUInteger result = bytesAvailable; + + if (maxLength > 0) + { + result = MIN(result, (maxLength - bytesDone)); + } + + // Should the data be read into the read packet's buffer, or into a pre-buffer first? + // + // One would imagine the preferred option is the faster one. + // So which one is faster? + // + // Reading directly into the packet's buffer requires: + // 1. Possibly resizing packet buffer (malloc/realloc) + // 2. Filling buffer (read) + // 3. Searching for term (memcmp) + // 4. Possibly copying overflow into prebuffer (malloc/realloc, memcpy) + // + // Reading into prebuffer first: + // 1. Possibly resizing prebuffer (malloc/realloc) + // 2. Filling buffer (read) + // 3. Searching for term (memcmp) + // 4. Copying underflow into packet buffer (malloc/realloc, memcpy) + // 5. Removing underflow from prebuffer (memmove) + // + // Comparing the performance of the two we can see that reading + // data into the prebuffer first is slower due to the extra memove. + // + // However: + // The implementation of NSMutableData is open source via core foundation's CFMutableData. + // Decreasing the length of a mutable data object doesn't cause a realloc. + // In other words, the capacity of a mutable data object can grow, but doesn't shrink. + // + // This means the prebuffer will rarely need a realloc. + // The packet buffer, on the other hand, may often need a realloc. + // This is especially true if we are the buffer owner. + // Furthermore, if we are constantly realloc'ing the packet buffer, + // and then moving the overflow into the prebuffer, + // then we're consistently over-allocating memory for each term read. + // And now we get into a bit of a tradeoff between speed and memory utilization. + // + // The end result is that the two perform very similarly. + // And we can answer the original question very simply by another means. + // + // If we can read all the data directly into the packet's buffer without resizing it first, + // then we do so. Otherwise we use the prebuffer. + + if (shouldPreBufferPtr) + { + NSUInteger buffSize = [buffer length]; + NSUInteger buffUsed = startOffset + bytesDone; + + if ((buffSize - buffUsed) >= result) + *shouldPreBufferPtr = NO; + else + *shouldPreBufferPtr = YES; + } + + return result; +} + +/** + * For read packets with a set terminator, + * returns the amount of data that can be read from the given preBuffer, + * without going over a terminator or the maxLength. + * + * It is assumed the terminator has not already been read. +**/ +- (NSUInteger)readLengthForTermWithPreBuffer:(GCDAsyncSocketPreBuffer *)preBuffer found:(BOOL *)foundPtr +{ + NSAssert(term != nil, @"This method does not apply to non-term reads"); + NSAssert([preBuffer availableBytes] > 0, @"Invoked with empty pre buffer!"); + + // We know that the terminator, as a whole, doesn't exist in our own buffer. + // But it is possible that a _portion_ of it exists in our buffer. + // So we're going to look for the terminator starting with a portion of our own buffer. + // + // Example: + // + // term length = 3 bytes + // bytesDone = 5 bytes + // preBuffer length = 5 bytes + // + // If we append the preBuffer to our buffer, + // it would look like this: + // + // --------------------- + // |B|B|B|B|B|P|P|P|P|P| + // --------------------- + // + // So we start our search here: + // + // --------------------- + // |B|B|B|B|B|P|P|P|P|P| + // -------^-^-^--------- + // + // And move forwards... + // + // --------------------- + // |B|B|B|B|B|P|P|P|P|P| + // ---------^-^-^------- + // + // Until we find the terminator or reach the end. + // + // --------------------- + // |B|B|B|B|B|P|P|P|P|P| + // ---------------^-^-^- + + BOOL found = NO; + + NSUInteger termLength = [term length]; + NSUInteger preBufferLength = [preBuffer availableBytes]; + + if ((bytesDone + preBufferLength) < termLength) + { + // Not enough data for a full term sequence yet + return preBufferLength; + } + + NSUInteger maxPreBufferLength; + if (maxLength > 0) { + maxPreBufferLength = MIN(preBufferLength, (maxLength - bytesDone)); + + // Note: maxLength >= termLength + } + else { + maxPreBufferLength = preBufferLength; + } + + uint8_t seq[termLength]; + const void *termBuf = [term bytes]; + + NSUInteger bufLen = MIN(bytesDone, (termLength - 1)); + uint8_t *buf = (uint8_t *)[buffer mutableBytes] + startOffset + bytesDone - bufLen; + + NSUInteger preLen = termLength - bufLen; + const uint8_t *pre = [preBuffer readBuffer]; + + NSUInteger loopCount = bufLen + maxPreBufferLength - termLength + 1; // Plus one. See example above. + + NSUInteger result = maxPreBufferLength; + + NSUInteger i; + for (i = 0; i < loopCount; i++) + { + if (bufLen > 0) + { + // Combining bytes from buffer and preBuffer + + memcpy(seq, buf, bufLen); + memcpy(seq + bufLen, pre, preLen); + + if (memcmp(seq, termBuf, termLength) == 0) + { + result = preLen; + found = YES; + break; + } + + buf++; + bufLen--; + preLen++; + } + else + { + // Comparing directly from preBuffer + + if (memcmp(pre, termBuf, termLength) == 0) + { + NSUInteger preOffset = pre - [preBuffer readBuffer]; // pointer arithmetic + + result = preOffset + termLength; + found = YES; + break; + } + + pre++; + } + } + + // There is no need to avoid resizing the buffer in this particular situation. + + if (foundPtr) *foundPtr = found; + return result; +} + +/** + * For read packets with a set terminator, scans the packet buffer for the term. + * It is assumed the terminator had not been fully read prior to the new bytes. + * + * If the term is found, the number of excess bytes after the term are returned. + * If the term is not found, this method will return -1. + * + * Note: A return value of zero means the term was found at the very end. + * + * Prerequisites: + * The given number of bytes have been added to the end of our buffer. + * Our bytesDone variable has NOT been changed due to the prebuffered bytes. +**/ +- (NSInteger)searchForTermAfterPreBuffering:(ssize_t)numBytes +{ + NSAssert(term != nil, @"This method does not apply to non-term reads"); + + // The implementation of this method is very similar to the above method. + // See the above method for a discussion of the algorithm used here. + + uint8_t *buff = [buffer mutableBytes]; + NSUInteger buffLength = bytesDone + numBytes; + + const void *termBuff = [term bytes]; + NSUInteger termLength = [term length]; + + // Note: We are dealing with unsigned integers, + // so make sure the math doesn't go below zero. + + NSUInteger i = ((buffLength - numBytes) >= termLength) ? (buffLength - numBytes - termLength + 1) : 0; + + while (i + termLength <= buffLength) + { + uint8_t *subBuffer = buff + startOffset + i; + + if (memcmp(subBuffer, termBuff, termLength) == 0) + { + return buffLength - (i + termLength); + } + + i++; + } + + return -1; +} + + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * The GCDAsyncWritePacket encompasses the instructions for any given write. +**/ +@interface GCDAsyncWritePacket : NSObject +{ + @public + NSData *buffer; + NSUInteger bytesDone; + long tag; + NSTimeInterval timeout; +} +- (id)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i; +@end + +@implementation GCDAsyncWritePacket + +- (id)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i +{ + if((self = [super init])) + { + buffer = d; // Retain not copy. For performance as documented in header file. + bytesDone = 0; + timeout = t; + tag = i; + } + return self; +} + + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * The GCDAsyncSpecialPacket encompasses special instructions for interruptions in the read/write queues. + * This class my be altered to support more than just TLS in the future. +**/ +@interface GCDAsyncSpecialPacket : NSObject +{ + @public + NSDictionary *tlsSettings; +} +- (id)initWithTLSSettings:(NSDictionary *)settings; +@end + +@implementation GCDAsyncSpecialPacket + +- (id)initWithTLSSettings:(NSDictionary *)settings +{ + if((self = [super init])) + { + tlsSettings = [settings copy]; + } + return self; +} + + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation GCDAsyncSocket +{ + uint32_t flags; + uint16_t config; + + __weak id delegate; + dispatch_queue_t delegateQueue; + + int socket4FD; + int socket6FD; + int socketUN; + NSURL *socketUrl; + int stateIndex; + NSData * connectInterface4; + NSData * connectInterface6; + NSData * connectInterfaceUN; + + dispatch_queue_t socketQueue; + + dispatch_source_t accept4Source; + dispatch_source_t accept6Source; + dispatch_source_t acceptUNSource; + dispatch_source_t connectTimer; + dispatch_source_t readSource; + dispatch_source_t writeSource; + dispatch_source_t readTimer; + dispatch_source_t writeTimer; + + NSMutableArray *readQueue; + NSMutableArray *writeQueue; + + GCDAsyncReadPacket *currentRead; + GCDAsyncWritePacket *currentWrite; + + unsigned long socketFDBytesAvailable; + + GCDAsyncSocketPreBuffer *preBuffer; + +#if TARGET_OS_IPHONE + CFStreamClientContext streamContext; + CFReadStreamRef readStream; + CFWriteStreamRef writeStream; +#endif + SSLContextRef sslContext; + GCDAsyncSocketPreBuffer *sslPreBuffer; + size_t sslWriteCachedLength; + OSStatus sslErrCode; + OSStatus lastSSLHandshakeError; + + void *IsOnSocketQueueOrTargetQueueKey; + + id userData; + NSTimeInterval alternateAddressDelay; +} + +- (id)init +{ + return [self initWithDelegate:nil delegateQueue:NULL socketQueue:NULL]; +} + +- (id)initWithSocketQueue:(dispatch_queue_t)sq +{ + return [self initWithDelegate:nil delegateQueue:NULL socketQueue:sq]; +} + +- (id)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq +{ + return [self initWithDelegate:aDelegate delegateQueue:dq socketQueue:NULL]; +} + +- (id)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq socketQueue:(dispatch_queue_t)sq +{ + if((self = [super init])) + { + delegate = aDelegate; + delegateQueue = dq; + + #if !OS_OBJECT_USE_OBJC + if (dq) dispatch_retain(dq); + #endif + + socket4FD = SOCKET_NULL; + socket6FD = SOCKET_NULL; + socketUN = SOCKET_NULL; + socketUrl = nil; + stateIndex = 0; + + if (sq) + { + NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), + @"The given socketQueue parameter must not be a concurrent queue."); + NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), + @"The given socketQueue parameter must not be a concurrent queue."); + NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), + @"The given socketQueue parameter must not be a concurrent queue."); + + socketQueue = sq; + #if !OS_OBJECT_USE_OBJC + dispatch_retain(sq); + #endif + } + else + { + socketQueue = dispatch_queue_create([GCDAsyncSocketQueueName UTF8String], NULL); + } + + // The dispatch_queue_set_specific() and dispatch_get_specific() functions take a "void *key" parameter. + // From the documentation: + // + // > Keys are only compared as pointers and are never dereferenced. + // > Thus, you can use a pointer to a static variable for a specific subsystem or + // > any other value that allows you to identify the value uniquely. + // + // We're just going to use the memory address of an ivar. + // Specifically an ivar that is explicitly named for our purpose to make the code more readable. + // + // However, it feels tedious (and less readable) to include the "&" all the time: + // dispatch_get_specific(&IsOnSocketQueueOrTargetQueueKey) + // + // So we're going to make it so it doesn't matter if we use the '&' or not, + // by assigning the value of the ivar to the address of the ivar. + // Thus: IsOnSocketQueueOrTargetQueueKey == &IsOnSocketQueueOrTargetQueueKey; + + IsOnSocketQueueOrTargetQueueKey = &IsOnSocketQueueOrTargetQueueKey; + + void *nonNullUnusedPointer = (__bridge void *)self; + dispatch_queue_set_specific(socketQueue, IsOnSocketQueueOrTargetQueueKey, nonNullUnusedPointer, NULL); + + readQueue = [[NSMutableArray alloc] initWithCapacity:5]; + currentRead = nil; + + writeQueue = [[NSMutableArray alloc] initWithCapacity:5]; + currentWrite = nil; + + preBuffer = [[GCDAsyncSocketPreBuffer alloc] initWithCapacity:(1024 * 4)]; + alternateAddressDelay = 0.3; + } + return self; +} + +- (void)dealloc +{ + LogInfo(@"%@ - %@ (start)", THIS_METHOD, self); + + // Set dealloc flag. + // This is used by closeWithError to ensure we don't accidentally retain ourself. + flags |= kDealloc; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + [self closeWithError:nil]; + } + else + { + dispatch_sync(socketQueue, ^{ + [self closeWithError:nil]; + }); + } + + delegate = nil; + + #if !OS_OBJECT_USE_OBJC + if (delegateQueue) dispatch_release(delegateQueue); + #endif + delegateQueue = NULL; + + #if !OS_OBJECT_USE_OBJC + if (socketQueue) dispatch_release(socketQueue); + #endif + socketQueue = NULL; + + LogInfo(@"%@ - %@ (finish)", THIS_METHOD, self); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Configuration +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (id)delegate +{ + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + return delegate; + } + else + { + __block id result; + + dispatch_sync(socketQueue, ^{ + result = delegate; + }); + + return result; + } +} + +- (void)setDelegate:(id)newDelegate synchronously:(BOOL)synchronously +{ + dispatch_block_t block = ^{ + delegate = newDelegate; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) { + block(); + } + else { + if (synchronously) + dispatch_sync(socketQueue, block); + else + dispatch_async(socketQueue, block); + } +} + +- (void)setDelegate:(id)newDelegate +{ + [self setDelegate:newDelegate synchronously:NO]; +} + +- (void)synchronouslySetDelegate:(id)newDelegate +{ + [self setDelegate:newDelegate synchronously:YES]; +} + +- (dispatch_queue_t)delegateQueue +{ + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + return delegateQueue; + } + else + { + __block dispatch_queue_t result; + + dispatch_sync(socketQueue, ^{ + result = delegateQueue; + }); + + return result; + } +} + +- (void)setDelegateQueue:(dispatch_queue_t)newDelegateQueue synchronously:(BOOL)synchronously +{ + dispatch_block_t block = ^{ + + #if !OS_OBJECT_USE_OBJC + if (delegateQueue) dispatch_release(delegateQueue); + if (newDelegateQueue) dispatch_retain(newDelegateQueue); + #endif + + delegateQueue = newDelegateQueue; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) { + block(); + } + else { + if (synchronously) + dispatch_sync(socketQueue, block); + else + dispatch_async(socketQueue, block); + } +} + +- (void)setDelegateQueue:(dispatch_queue_t)newDelegateQueue +{ + [self setDelegateQueue:newDelegateQueue synchronously:NO]; +} + +- (void)synchronouslySetDelegateQueue:(dispatch_queue_t)newDelegateQueue +{ + [self setDelegateQueue:newDelegateQueue synchronously:YES]; +} + +- (void)getDelegate:(id *)delegatePtr delegateQueue:(dispatch_queue_t *)delegateQueuePtr +{ + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + if (delegatePtr) *delegatePtr = delegate; + if (delegateQueuePtr) *delegateQueuePtr = delegateQueue; + } + else + { + __block id dPtr = NULL; + __block dispatch_queue_t dqPtr = NULL; + + dispatch_sync(socketQueue, ^{ + dPtr = delegate; + dqPtr = delegateQueue; + }); + + if (delegatePtr) *delegatePtr = dPtr; + if (delegateQueuePtr) *delegateQueuePtr = dqPtr; + } +} + +- (void)setDelegate:(id)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue synchronously:(BOOL)synchronously +{ + dispatch_block_t block = ^{ + + delegate = newDelegate; + + #if !OS_OBJECT_USE_OBJC + if (delegateQueue) dispatch_release(delegateQueue); + if (newDelegateQueue) dispatch_retain(newDelegateQueue); + #endif + + delegateQueue = newDelegateQueue; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) { + block(); + } + else { + if (synchronously) + dispatch_sync(socketQueue, block); + else + dispatch_async(socketQueue, block); + } +} + +- (void)setDelegate:(id)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue +{ + [self setDelegate:newDelegate delegateQueue:newDelegateQueue synchronously:NO]; +} + +- (void)synchronouslySetDelegate:(id)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue +{ + [self setDelegate:newDelegate delegateQueue:newDelegateQueue synchronously:YES]; +} + +- (BOOL)isIPv4Enabled +{ + // Note: YES means kIPv4Disabled is OFF + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + return ((config & kIPv4Disabled) == 0); + } + else + { + __block BOOL result; + + dispatch_sync(socketQueue, ^{ + result = ((config & kIPv4Disabled) == 0); + }); + + return result; + } +} + +- (void)setIPv4Enabled:(BOOL)flag +{ + // Note: YES means kIPv4Disabled is OFF + + dispatch_block_t block = ^{ + + if (flag) + config &= ~kIPv4Disabled; + else + config |= kIPv4Disabled; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_async(socketQueue, block); +} + +- (BOOL)isIPv6Enabled +{ + // Note: YES means kIPv6Disabled is OFF + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + return ((config & kIPv6Disabled) == 0); + } + else + { + __block BOOL result; + + dispatch_sync(socketQueue, ^{ + result = ((config & kIPv6Disabled) == 0); + }); + + return result; + } +} + +- (void)setIPv6Enabled:(BOOL)flag +{ + // Note: YES means kIPv6Disabled is OFF + + dispatch_block_t block = ^{ + + if (flag) + config &= ~kIPv6Disabled; + else + config |= kIPv6Disabled; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_async(socketQueue, block); +} + +- (BOOL)isIPv4PreferredOverIPv6 +{ + // Note: YES means kPreferIPv6 is OFF + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + return ((config & kPreferIPv6) == 0); + } + else + { + __block BOOL result; + + dispatch_sync(socketQueue, ^{ + result = ((config & kPreferIPv6) == 0); + }); + + return result; + } +} + +- (void)setIPv4PreferredOverIPv6:(BOOL)flag +{ + // Note: YES means kPreferIPv6 is OFF + + dispatch_block_t block = ^{ + + if (flag) + config &= ~kPreferIPv6; + else + config |= kPreferIPv6; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_async(socketQueue, block); +} + +- (NSTimeInterval) alternateAddressDelay { + __block NSTimeInterval delay; + dispatch_block_t block = ^{ + delay = alternateAddressDelay; + }; + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + return delay; +} + +- (void) setAlternateAddressDelay:(NSTimeInterval)delay { + dispatch_block_t block = ^{ + alternateAddressDelay = delay; + }; + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_async(socketQueue, block); +} + +- (id)userData +{ + __block id result = nil; + + dispatch_block_t block = ^{ + + result = userData; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + return result; +} + +- (void)setUserData:(id)arbitraryUserData +{ + dispatch_block_t block = ^{ + + if (userData != arbitraryUserData) + { + userData = arbitraryUserData; + } + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_async(socketQueue, block); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Accepting +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (BOOL)acceptOnPort:(uint16_t)port error:(NSError **)errPtr +{ + return [self acceptOnInterface:nil port:port error:errPtr]; +} + +- (BOOL)acceptOnInterface:(NSString *)inInterface port:(uint16_t)port error:(NSError **)errPtr +{ + LogTrace(); + + // Just in-case interface parameter is immutable. + NSString *interface = [inInterface copy]; + + __block BOOL result = NO; + __block NSError *err = nil; + + // CreateSocket Block + // This block will be invoked within the dispatch block below. + + int(^createSocket)(int, NSData*) = ^int (int domain, NSData *interfaceAddr) { + + int socketFD = socket(domain, SOCK_STREAM, 0); + + if (socketFD == SOCKET_NULL) + { + NSString *reason = @"Error in socket() function"; + err = [self errnoErrorWithReason:reason]; + + return SOCKET_NULL; + } + + int status; + + // Set socket options + + status = fcntl(socketFD, F_SETFL, O_NONBLOCK); + if (status == -1) + { + NSString *reason = @"Error enabling non-blocking IO on socket (fcntl)"; + err = [self errnoErrorWithReason:reason]; + + LogVerbose(@"close(socketFD)"); + close(socketFD); + return SOCKET_NULL; + } + + int reuseOn = 1; + status = setsockopt(socketFD, SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn)); + if (status == -1) + { + NSString *reason = @"Error enabling address reuse (setsockopt)"; + err = [self errnoErrorWithReason:reason]; + + LogVerbose(@"close(socketFD)"); + close(socketFD); + return SOCKET_NULL; + } + + // Bind socket + + status = bind(socketFD, (const struct sockaddr *)[interfaceAddr bytes], (socklen_t)[interfaceAddr length]); + if (status == -1) + { + NSString *reason = @"Error in bind() function"; + err = [self errnoErrorWithReason:reason]; + + LogVerbose(@"close(socketFD)"); + close(socketFD); + return SOCKET_NULL; + } + + // Listen + + status = listen(socketFD, 1024); + if (status == -1) + { + NSString *reason = @"Error in listen() function"; + err = [self errnoErrorWithReason:reason]; + + LogVerbose(@"close(socketFD)"); + close(socketFD); + return SOCKET_NULL; + } + + return socketFD; + }; + + // Create dispatch block and run on socketQueue + + dispatch_block_t block = ^{ @autoreleasepool { + + if (delegate == nil) // Must have delegate set + { + NSString *msg = @"Attempting to accept without a delegate. Set a delegate first."; + err = [self badConfigError:msg]; + + return_from_block; + } + + if (delegateQueue == NULL) // Must have delegate queue set + { + NSString *msg = @"Attempting to accept without a delegate queue. Set a delegate queue first."; + err = [self badConfigError:msg]; + + return_from_block; + } + + BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO; + BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO; + + if (isIPv4Disabled && isIPv6Disabled) // Must have IPv4 or IPv6 enabled + { + NSString *msg = @"Both IPv4 and IPv6 have been disabled. Must enable at least one protocol first."; + err = [self badConfigError:msg]; + + return_from_block; + } + + if (![self isDisconnected]) // Must be disconnected + { + NSString *msg = @"Attempting to accept while connected or accepting connections. Disconnect first."; + err = [self badConfigError:msg]; + + return_from_block; + } + + // Clear queues (spurious read/write requests post disconnect) + [readQueue removeAllObjects]; + [writeQueue removeAllObjects]; + + // Resolve interface from description + + NSMutableData *interface4 = nil; + NSMutableData *interface6 = nil; + + [self getInterfaceAddress4:&interface4 address6:&interface6 fromDescription:interface port:port]; + + if ((interface4 == nil) && (interface6 == nil)) + { + NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\") or IP address."; + err = [self badParamError:msg]; + + return_from_block; + } + + if (isIPv4Disabled && (interface6 == nil)) + { + NSString *msg = @"IPv4 has been disabled and specified interface doesn't support IPv6."; + err = [self badParamError:msg]; + + return_from_block; + } + + if (isIPv6Disabled && (interface4 == nil)) + { + NSString *msg = @"IPv6 has been disabled and specified interface doesn't support IPv4."; + err = [self badParamError:msg]; + + return_from_block; + } + + BOOL enableIPv4 = !isIPv4Disabled && (interface4 != nil); + BOOL enableIPv6 = !isIPv6Disabled && (interface6 != nil); + + // Create sockets, configure, bind, and listen + + if (enableIPv4) + { + LogVerbose(@"Creating IPv4 socket"); + socket4FD = createSocket(AF_INET, interface4); + + if (socket4FD == SOCKET_NULL) + { + return_from_block; + } + } + + if (enableIPv6) + { + LogVerbose(@"Creating IPv6 socket"); + + if (enableIPv4 && (port == 0)) + { + // No specific port was specified, so we allowed the OS to pick an available port for us. + // Now we need to make sure the IPv6 socket listens on the same port as the IPv4 socket. + + struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)[interface6 mutableBytes]; + addr6->sin6_port = htons([self localPort4]); + } + + socket6FD = createSocket(AF_INET6, interface6); + + if (socket6FD == SOCKET_NULL) + { + if (socket4FD != SOCKET_NULL) + { + LogVerbose(@"close(socket4FD)"); + close(socket4FD); + } + + return_from_block; + } + } + + // Create accept sources + + if (enableIPv4) + { + accept4Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, socket4FD, 0, socketQueue); + + int socketFD = socket4FD; + dispatch_source_t acceptSource = accept4Source; + + __weak GCDAsyncSocket *weakSelf = self; + + dispatch_source_set_event_handler(accept4Source, ^{ @autoreleasepool { + #pragma clang diagnostic push + #pragma clang diagnostic warning "-Wimplicit-retain-self" + + __strong GCDAsyncSocket *strongSelf = weakSelf; + if (strongSelf == nil) return_from_block; + + LogVerbose(@"event4Block"); + + unsigned long i = 0; + unsigned long numPendingConnections = dispatch_source_get_data(acceptSource); + + LogVerbose(@"numPendingConnections: %lu", numPendingConnections); + + while ([strongSelf doAccept:socketFD] && (++i < numPendingConnections)); + + #pragma clang diagnostic pop + }}); + + + dispatch_source_set_cancel_handler(accept4Source, ^{ + #pragma clang diagnostic push + #pragma clang diagnostic warning "-Wimplicit-retain-self" + + #if !OS_OBJECT_USE_OBJC + LogVerbose(@"dispatch_release(accept4Source)"); + dispatch_release(acceptSource); + #endif + + LogVerbose(@"close(socket4FD)"); + close(socketFD); + + #pragma clang diagnostic pop + }); + + LogVerbose(@"dispatch_resume(accept4Source)"); + dispatch_resume(accept4Source); + } + + if (enableIPv6) + { + accept6Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, socket6FD, 0, socketQueue); + + int socketFD = socket6FD; + dispatch_source_t acceptSource = accept6Source; + + __weak GCDAsyncSocket *weakSelf = self; + + dispatch_source_set_event_handler(accept6Source, ^{ @autoreleasepool { + #pragma clang diagnostic push + #pragma clang diagnostic warning "-Wimplicit-retain-self" + + __strong GCDAsyncSocket *strongSelf = weakSelf; + if (strongSelf == nil) return_from_block; + + LogVerbose(@"event6Block"); + + unsigned long i = 0; + unsigned long numPendingConnections = dispatch_source_get_data(acceptSource); + + LogVerbose(@"numPendingConnections: %lu", numPendingConnections); + + while ([strongSelf doAccept:socketFD] && (++i < numPendingConnections)); + + #pragma clang diagnostic pop + }}); + + dispatch_source_set_cancel_handler(accept6Source, ^{ + #pragma clang diagnostic push + #pragma clang diagnostic warning "-Wimplicit-retain-self" + + #if !OS_OBJECT_USE_OBJC + LogVerbose(@"dispatch_release(accept6Source)"); + dispatch_release(acceptSource); + #endif + + LogVerbose(@"close(socket6FD)"); + close(socketFD); + + #pragma clang diagnostic pop + }); + + LogVerbose(@"dispatch_resume(accept6Source)"); + dispatch_resume(accept6Source); + } + + flags |= kSocketStarted; + + result = YES; + }}; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + if (result == NO) + { + LogInfo(@"Error in accept: %@", err); + + if (errPtr) + *errPtr = err; + } + + return result; +} + +- (BOOL)acceptOnUrl:(NSURL *)url error:(NSError **)errPtr; +{ + LogTrace(); + + __block BOOL result = NO; + __block NSError *err = nil; + + // CreateSocket Block + // This block will be invoked within the dispatch block below. + + int(^createSocket)(int, NSData*) = ^int (int domain, NSData *interfaceAddr) { + + int socketFD = socket(domain, SOCK_STREAM, 0); + + if (socketFD == SOCKET_NULL) + { + NSString *reason = @"Error in socket() function"; + err = [self errnoErrorWithReason:reason]; + + return SOCKET_NULL; + } + + int status; + + // Set socket options + + status = fcntl(socketFD, F_SETFL, O_NONBLOCK); + if (status == -1) + { + NSString *reason = @"Error enabling non-blocking IO on socket (fcntl)"; + err = [self errnoErrorWithReason:reason]; + + LogVerbose(@"close(socketFD)"); + close(socketFD); + return SOCKET_NULL; + } + + int reuseOn = 1; + status = setsockopt(socketFD, SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn)); + if (status == -1) + { + NSString *reason = @"Error enabling address reuse (setsockopt)"; + err = [self errnoErrorWithReason:reason]; + + LogVerbose(@"close(socketFD)"); + close(socketFD); + return SOCKET_NULL; + } + + // Bind socket + + status = bind(socketFD, (const struct sockaddr *)[interfaceAddr bytes], (socklen_t)[interfaceAddr length]); + if (status == -1) + { + NSString *reason = @"Error in bind() function"; + err = [self errnoErrorWithReason:reason]; + + LogVerbose(@"close(socketFD)"); + close(socketFD); + return SOCKET_NULL; + } + + // Listen + + status = listen(socketFD, 1024); + if (status == -1) + { + NSString *reason = @"Error in listen() function"; + err = [self errnoErrorWithReason:reason]; + + LogVerbose(@"close(socketFD)"); + close(socketFD); + return SOCKET_NULL; + } + + return socketFD; + }; + + // Create dispatch block and run on socketQueue + + dispatch_block_t block = ^{ @autoreleasepool { + + if (delegate == nil) // Must have delegate set + { + NSString *msg = @"Attempting to accept without a delegate. Set a delegate first."; + err = [self badConfigError:msg]; + + return_from_block; + } + + if (delegateQueue == NULL) // Must have delegate queue set + { + NSString *msg = @"Attempting to accept without a delegate queue. Set a delegate queue first."; + err = [self badConfigError:msg]; + + return_from_block; + } + + if (![self isDisconnected]) // Must be disconnected + { + NSString *msg = @"Attempting to accept while connected or accepting connections. Disconnect first."; + err = [self badConfigError:msg]; + + return_from_block; + } + + // Clear queues (spurious read/write requests post disconnect) + [readQueue removeAllObjects]; + [writeQueue removeAllObjects]; + + // Remove a previous socket + + NSError *error = nil; + NSFileManager *fileManager = [NSFileManager defaultManager]; + if ([fileManager fileExistsAtPath:url.path]) { + if (![[NSFileManager defaultManager] removeItemAtURL:url error:&error]) { + NSString *msg = @"Could not remove previous unix domain socket at given url."; + err = [self otherError:msg]; + + return_from_block; + } + } + + // Resolve interface from description + + NSData *interface = [self getInterfaceAddressFromUrl:url]; + + if (interface == nil) + { + NSString *msg = @"Invalid unix domain url. Specify a valid file url that does not exist (e.g. \"file:///tmp/socket\")"; + err = [self badParamError:msg]; + + return_from_block; + } + + // Create sockets, configure, bind, and listen + + LogVerbose(@"Creating unix domain socket"); + socketUN = createSocket(AF_UNIX, interface); + + if (socketUN == SOCKET_NULL) + { + return_from_block; + } + + socketUrl = url; + + // Create accept sources + + acceptUNSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, socketUN, 0, socketQueue); + + int socketFD = socketUN; + dispatch_source_t acceptSource = acceptUNSource; + + dispatch_source_set_event_handler(acceptUNSource, ^{ @autoreleasepool { + + LogVerbose(@"eventUNBlock"); + + unsigned long i = 0; + unsigned long numPendingConnections = dispatch_source_get_data(acceptSource); + + LogVerbose(@"numPendingConnections: %lu", numPendingConnections); + + while ([self doAccept:socketFD] && (++i < numPendingConnections)); + }}); + + dispatch_source_set_cancel_handler(acceptUNSource, ^{ + +#if NEEDS_DISPATCH_RETAIN_RELEASE + LogVerbose(@"dispatch_release(accept4Source)"); + dispatch_release(acceptSource); +#endif + + LogVerbose(@"close(socket4FD)"); + close(socketFD); + }); + + LogVerbose(@"dispatch_resume(accept4Source)"); + dispatch_resume(acceptUNSource); + + flags |= kSocketStarted; + + result = YES; + }}; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + if (result == NO) + { + LogInfo(@"Error in accept: %@", err); + + if (errPtr) + *errPtr = err; + } + + return result; +} + +- (BOOL)doAccept:(int)parentSocketFD +{ + LogTrace(); + + int socketType; + int childSocketFD; + NSData *childSocketAddress; + + if (parentSocketFD == socket4FD) + { + socketType = 0; + + struct sockaddr_in addr; + socklen_t addrLen = sizeof(addr); + + childSocketFD = accept(parentSocketFD, (struct sockaddr *)&addr, &addrLen); + + if (childSocketFD == -1) + { + LogWarn(@"Accept failed with error: %@", [self errnoError]); + return NO; + } + + childSocketAddress = [NSData dataWithBytes:&addr length:addrLen]; + } + else if (parentSocketFD == socket6FD) + { + socketType = 1; + + struct sockaddr_in6 addr; + socklen_t addrLen = sizeof(addr); + + childSocketFD = accept(parentSocketFD, (struct sockaddr *)&addr, &addrLen); + + if (childSocketFD == -1) + { + LogWarn(@"Accept failed with error: %@", [self errnoError]); + return NO; + } + + childSocketAddress = [NSData dataWithBytes:&addr length:addrLen]; + } + else // if (parentSocketFD == socketUN) + { + socketType = 2; + + struct sockaddr_un addr; + socklen_t addrLen = sizeof(addr); + + childSocketFD = accept(parentSocketFD, (struct sockaddr *)&addr, &addrLen); + + if (childSocketFD == -1) + { + LogWarn(@"Accept failed with error: %@", [self errnoError]); + return NO; + } + + childSocketAddress = [NSData dataWithBytes:&addr length:addrLen]; + } + + // Enable non-blocking IO on the socket + + int result = fcntl(childSocketFD, F_SETFL, O_NONBLOCK); + if (result == -1) + { + LogWarn(@"Error enabling non-blocking IO on accepted socket (fcntl)"); + return NO; + } + + // Prevent SIGPIPE signals + + int nosigpipe = 1; + setsockopt(childSocketFD, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe)); + + // Notify delegate + + if (delegateQueue) + { + __strong id theDelegate = delegate; + + dispatch_async(delegateQueue, ^{ @autoreleasepool { + + // Query delegate for custom socket queue + + dispatch_queue_t childSocketQueue = NULL; + + if ([theDelegate respondsToSelector:@selector(newSocketQueueForConnectionFromAddress:onSocket:)]) + { + childSocketQueue = [theDelegate newSocketQueueForConnectionFromAddress:childSocketAddress + onSocket:self]; + } + + // Create GCDAsyncSocket instance for accepted socket + + GCDAsyncSocket *acceptedSocket = [[[self class] alloc] initWithDelegate:theDelegate + delegateQueue:delegateQueue + socketQueue:childSocketQueue]; + + if (socketType == 0) + acceptedSocket->socket4FD = childSocketFD; + else if (socketType == 1) + acceptedSocket->socket6FD = childSocketFD; + else + acceptedSocket->socketUN = childSocketFD; + + acceptedSocket->flags = (kSocketStarted | kConnected); + + // Setup read and write sources for accepted socket + + dispatch_async(acceptedSocket->socketQueue, ^{ @autoreleasepool { + + [acceptedSocket setupReadAndWriteSourcesForNewlyConnectedSocket:childSocketFD]; + }}); + + // Notify delegate + + if ([theDelegate respondsToSelector:@selector(socket:didAcceptNewSocket:)]) + { + [theDelegate socket:self didAcceptNewSocket:acceptedSocket]; + } + + // Release the socket queue returned from the delegate (it was retained by acceptedSocket) + #if !OS_OBJECT_USE_OBJC + if (childSocketQueue) dispatch_release(childSocketQueue); + #endif + + // The accepted socket should have been retained by the delegate. + // Otherwise it gets properly released when exiting the block. + }}); + } + + return YES; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Connecting +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * This method runs through the various checks required prior to a connection attempt. + * It is shared between the connectToHost and connectToAddress methods. + * +**/ +- (BOOL)preConnectWithInterface:(NSString *)interface error:(NSError **)errPtr +{ + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + + if (delegate == nil) // Must have delegate set + { + if (errPtr) + { + NSString *msg = @"Attempting to connect without a delegate. Set a delegate first."; + *errPtr = [self badConfigError:msg]; + } + return NO; + } + + if (delegateQueue == NULL) // Must have delegate queue set + { + if (errPtr) + { + NSString *msg = @"Attempting to connect without a delegate queue. Set a delegate queue first."; + *errPtr = [self badConfigError:msg]; + } + return NO; + } + + if (![self isDisconnected]) // Must be disconnected + { + if (errPtr) + { + NSString *msg = @"Attempting to connect while connected or accepting connections. Disconnect first."; + *errPtr = [self badConfigError:msg]; + } + return NO; + } + + BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO; + BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO; + + if (isIPv4Disabled && isIPv6Disabled) // Must have IPv4 or IPv6 enabled + { + if (errPtr) + { + NSString *msg = @"Both IPv4 and IPv6 have been disabled. Must enable at least one protocol first."; + *errPtr = [self badConfigError:msg]; + } + return NO; + } + + if (interface) + { + NSMutableData *interface4 = nil; + NSMutableData *interface6 = nil; + + [self getInterfaceAddress4:&interface4 address6:&interface6 fromDescription:interface port:0]; + + if ((interface4 == nil) && (interface6 == nil)) + { + if (errPtr) + { + NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\") or IP address."; + *errPtr = [self badParamError:msg]; + } + return NO; + } + + if (isIPv4Disabled && (interface6 == nil)) + { + if (errPtr) + { + NSString *msg = @"IPv4 has been disabled and specified interface doesn't support IPv6."; + *errPtr = [self badParamError:msg]; + } + return NO; + } + + if (isIPv6Disabled && (interface4 == nil)) + { + if (errPtr) + { + NSString *msg = @"IPv6 has been disabled and specified interface doesn't support IPv4."; + *errPtr = [self badParamError:msg]; + } + return NO; + } + + connectInterface4 = interface4; + connectInterface6 = interface6; + } + + // Clear queues (spurious read/write requests post disconnect) + [readQueue removeAllObjects]; + [writeQueue removeAllObjects]; + + return YES; +} + +- (BOOL)preConnectWithUrl:(NSURL *)url error:(NSError **)errPtr +{ + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + + if (delegate == nil) // Must have delegate set + { + if (errPtr) + { + NSString *msg = @"Attempting to connect without a delegate. Set a delegate first."; + *errPtr = [self badConfigError:msg]; + } + return NO; + } + + if (delegateQueue == NULL) // Must have delegate queue set + { + if (errPtr) + { + NSString *msg = @"Attempting to connect without a delegate queue. Set a delegate queue first."; + *errPtr = [self badConfigError:msg]; + } + return NO; + } + + if (![self isDisconnected]) // Must be disconnected + { + if (errPtr) + { + NSString *msg = @"Attempting to connect while connected or accepting connections. Disconnect first."; + *errPtr = [self badConfigError:msg]; + } + return NO; + } + + NSData *interface = [self getInterfaceAddressFromUrl:url]; + + if (interface == nil) + { + if (errPtr) + { + NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\") or IP address."; + *errPtr = [self badParamError:msg]; + } + return NO; + } + + connectInterfaceUN = interface; + + // Clear queues (spurious read/write requests post disconnect) + [readQueue removeAllObjects]; + [writeQueue removeAllObjects]; + + return YES; +} + +- (BOOL)connectToHost:(NSString*)host onPort:(uint16_t)port error:(NSError **)errPtr +{ + return [self connectToHost:host onPort:port withTimeout:-1 error:errPtr]; +} + +- (BOOL)connectToHost:(NSString *)host + onPort:(uint16_t)port + withTimeout:(NSTimeInterval)timeout + error:(NSError **)errPtr +{ + return [self connectToHost:host onPort:port viaInterface:nil withTimeout:timeout error:errPtr]; +} + +- (BOOL)connectToHost:(NSString *)inHost + onPort:(uint16_t)port + viaInterface:(NSString *)inInterface + withTimeout:(NSTimeInterval)timeout + error:(NSError **)errPtr +{ + LogTrace(); + + // Just in case immutable objects were passed + NSString *host = [inHost copy]; + NSString *interface = [inInterface copy]; + + __block BOOL result = NO; + __block NSError *preConnectErr = nil; + + dispatch_block_t block = ^{ @autoreleasepool { + + // Check for problems with host parameter + + if ([host length] == 0) + { + NSString *msg = @"Invalid host parameter (nil or \"\"). Should be a domain name or IP address string."; + preConnectErr = [self badParamError:msg]; + + return_from_block; + } + + // Run through standard pre-connect checks + + if (![self preConnectWithInterface:interface error:&preConnectErr]) + { + return_from_block; + } + + // We've made it past all the checks. + // It's time to start the connection process. + + flags |= kSocketStarted; + + LogVerbose(@"Dispatching DNS lookup..."); + + // It's possible that the given host parameter is actually a NSMutableString. + // So we want to copy it now, within this block that will be executed synchronously. + // This way the asynchronous lookup block below doesn't have to worry about it changing. + + NSString *hostCpy = [host copy]; + + int aStateIndex = stateIndex; + __weak GCDAsyncSocket *weakSelf = self; + + dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); + dispatch_async(globalConcurrentQueue, ^{ @autoreleasepool { + #pragma clang diagnostic push + #pragma clang diagnostic warning "-Wimplicit-retain-self" + + NSError *lookupErr = nil; + NSMutableArray *addresses = [[self class] lookupHost:hostCpy port:port error:&lookupErr]; + + __strong GCDAsyncSocket *strongSelf = weakSelf; + if (strongSelf == nil) return_from_block; + + if (lookupErr) + { + dispatch_async(strongSelf->socketQueue, ^{ @autoreleasepool { + + [strongSelf lookup:aStateIndex didFail:lookupErr]; + }}); + } + else + { + NSData *address4 = nil; + NSData *address6 = nil; + + for (NSData *address in addresses) + { + if (!address4 && [[self class] isIPv4Address:address]) + { + address4 = address; + } + else if (!address6 && [[self class] isIPv6Address:address]) + { + address6 = address; + } + } + + dispatch_async(strongSelf->socketQueue, ^{ @autoreleasepool { + + [strongSelf lookup:aStateIndex didSucceedWithAddress4:address4 address6:address6]; + }}); + } + + #pragma clang diagnostic pop + }}); + + [self startConnectTimeout:timeout]; + + result = YES; + }}; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + + if (errPtr) *errPtr = preConnectErr; + return result; +} + +- (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr +{ + return [self connectToAddress:remoteAddr viaInterface:nil withTimeout:-1 error:errPtr]; +} + +- (BOOL)connectToAddress:(NSData *)remoteAddr withTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr +{ + return [self connectToAddress:remoteAddr viaInterface:nil withTimeout:timeout error:errPtr]; +} + +- (BOOL)connectToAddress:(NSData *)inRemoteAddr + viaInterface:(NSString *)inInterface + withTimeout:(NSTimeInterval)timeout + error:(NSError **)errPtr +{ + LogTrace(); + + // Just in case immutable objects were passed + NSData *remoteAddr = [inRemoteAddr copy]; + NSString *interface = [inInterface copy]; + + __block BOOL result = NO; + __block NSError *err = nil; + + dispatch_block_t block = ^{ @autoreleasepool { + + // Check for problems with remoteAddr parameter + + NSData *address4 = nil; + NSData *address6 = nil; + + if ([remoteAddr length] >= sizeof(struct sockaddr)) + { + const struct sockaddr *sockaddr = (const struct sockaddr *)[remoteAddr bytes]; + + if (sockaddr->sa_family == AF_INET) + { + if ([remoteAddr length] == sizeof(struct sockaddr_in)) + { + address4 = remoteAddr; + } + } + else if (sockaddr->sa_family == AF_INET6) + { + if ([remoteAddr length] == sizeof(struct sockaddr_in6)) + { + address6 = remoteAddr; + } + } + } + + if ((address4 == nil) && (address6 == nil)) + { + NSString *msg = @"A valid IPv4 or IPv6 address was not given"; + err = [self badParamError:msg]; + + return_from_block; + } + + BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO; + BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO; + + if (isIPv4Disabled && (address4 != nil)) + { + NSString *msg = @"IPv4 has been disabled and an IPv4 address was passed."; + err = [self badParamError:msg]; + + return_from_block; + } + + if (isIPv6Disabled && (address6 != nil)) + { + NSString *msg = @"IPv6 has been disabled and an IPv6 address was passed."; + err = [self badParamError:msg]; + + return_from_block; + } + + // Run through standard pre-connect checks + + if (![self preConnectWithInterface:interface error:&err]) + { + return_from_block; + } + + // We've made it past all the checks. + // It's time to start the connection process. + + if (![self connectWithAddress4:address4 address6:address6 error:&err]) + { + return_from_block; + } + + flags |= kSocketStarted; + + [self startConnectTimeout:timeout]; + + result = YES; + }}; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + if (result == NO) + { + if (errPtr) + *errPtr = err; + } + + return result; +} + +- (BOOL)connectToUrl:(NSURL *)url withTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr; +{ + LogTrace(); + + __block BOOL result = NO; + __block NSError *err = nil; + + dispatch_block_t block = ^{ @autoreleasepool { + + // Check for problems with host parameter + + if ([url.path length] == 0) + { + NSString *msg = @"Invalid unix domain socket url."; + err = [self badParamError:msg]; + + return_from_block; + } + + // Run through standard pre-connect checks + + if (![self preConnectWithUrl:url error:&err]) + { + return_from_block; + } + + // We've made it past all the checks. + // It's time to start the connection process. + + flags |= kSocketStarted; + + // Start the normal connection process + + NSError *connectError = nil; + if (![self connectWithAddressUN:connectInterfaceUN error:&connectError]) + { + [self closeWithError:connectError]; + + return_from_block; + } + + [self startConnectTimeout:timeout]; + + result = YES; + }}; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + if (result == NO) + { + if (errPtr) + *errPtr = err; + } + + return result; +} + +- (void)lookup:(int)aStateIndex didSucceedWithAddress4:(NSData *)address4 address6:(NSData *)address6 +{ + LogTrace(); + + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + NSAssert(address4 || address6, @"Expected at least one valid address"); + + if (aStateIndex != stateIndex) + { + LogInfo(@"Ignoring lookupDidSucceed, already disconnected"); + + // The connect operation has been cancelled. + // That is, socket was disconnected, or connection has already timed out. + return; + } + + // Check for problems + + BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO; + BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO; + + if (isIPv4Disabled && (address6 == nil)) + { + NSString *msg = @"IPv4 has been disabled and DNS lookup found no IPv6 address."; + + [self closeWithError:[self otherError:msg]]; + return; + } + + if (isIPv6Disabled && (address4 == nil)) + { + NSString *msg = @"IPv6 has been disabled and DNS lookup found no IPv4 address."; + + [self closeWithError:[self otherError:msg]]; + return; + } + + // Start the normal connection process + + NSError *err = nil; + if (![self connectWithAddress4:address4 address6:address6 error:&err]) + { + [self closeWithError:err]; + } +} + +/** + * This method is called if the DNS lookup fails. + * This method is executed on the socketQueue. + * + * Since the DNS lookup executed synchronously on a global concurrent queue, + * the original connection request may have already been cancelled or timed-out by the time this method is invoked. + * The lookupIndex tells us whether the lookup is still valid or not. +**/ +- (void)lookup:(int)aStateIndex didFail:(NSError *)error +{ + LogTrace(); + + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + + + if (aStateIndex != stateIndex) + { + LogInfo(@"Ignoring lookup:didFail: - already disconnected"); + + // The connect operation has been cancelled. + // That is, socket was disconnected, or connection has already timed out. + return; + } + + [self endConnectTimeout]; + [self closeWithError:error]; +} + +- (BOOL)bindSocket:(int)socketFD toInterface:(NSData *)connectInterface error:(NSError **)errPtr +{ + // Bind the socket to the desired interface (if needed) + + if (connectInterface) + { + LogVerbose(@"Binding socket..."); + + if ([[self class] portFromAddress:connectInterface] > 0) + { + // Since we're going to be binding to a specific port, + // we should turn on reuseaddr to allow us to override sockets in time_wait. + + int reuseOn = 1; + setsockopt(socketFD, SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn)); + } + + const struct sockaddr *interfaceAddr = (const struct sockaddr *)[connectInterface bytes]; + + int result = bind(socketFD, interfaceAddr, (socklen_t)[connectInterface length]); + if (result != 0) + { + if (errPtr) + *errPtr = [self errnoErrorWithReason:@"Error in bind() function"]; + + return NO; + } + } + + return YES; +} + +- (int)createSocket:(int)family connectInterface:(NSData *)connectInterface errPtr:(NSError **)errPtr +{ + int socketFD = socket(family, SOCK_STREAM, 0); + + if (socketFD == SOCKET_NULL) + { + if (errPtr) + *errPtr = [self errnoErrorWithReason:@"Error in socket() function"]; + + return socketFD; + } + + if (![self bindSocket:socketFD toInterface:connectInterface error:errPtr]) + { + [self closeSocket:socketFD]; + + return SOCKET_NULL; + } + + // Prevent SIGPIPE signals + + int nosigpipe = 1; + setsockopt(socketFD, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe)); + + return socketFD; +} + +- (void)connectSocket:(int)socketFD address:(NSData *)address stateIndex:(int)aStateIndex +{ + // If there already is a socket connected, we close socketFD and return + if (self.isConnected) + { + [self closeSocket:socketFD]; + return; + } + + // Start the connection process in a background queue + + __weak GCDAsyncSocket *weakSelf = self; + + dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); + dispatch_async(globalConcurrentQueue, ^{ +#pragma clang diagnostic push +#pragma clang diagnostic warning "-Wimplicit-retain-self" + + int result = connect(socketFD, (const struct sockaddr *)[address bytes], (socklen_t)[address length]); + + __strong GCDAsyncSocket *strongSelf = weakSelf; + if (strongSelf == nil) return_from_block; + + dispatch_async(strongSelf->socketQueue, ^{ @autoreleasepool { + + if (strongSelf.isConnected) + { + [strongSelf closeSocket:socketFD]; + return_from_block; + } + + if (result == 0) + { + [self closeUnusedSocket:socketFD]; + + [strongSelf didConnect:aStateIndex]; + } + else + { + [strongSelf closeSocket:socketFD]; + + // If there are no more sockets trying to connect, we inform the error to the delegate + if (strongSelf.socket4FD == SOCKET_NULL && strongSelf.socket6FD == SOCKET_NULL) + { + NSError *error = [strongSelf errnoErrorWithReason:@"Error in connect() function"]; + [strongSelf didNotConnect:aStateIndex error:error]; + } + } + }}); + +#pragma clang diagnostic pop + }); + + LogVerbose(@"Connecting..."); +} + +- (void)closeSocket:(int)socketFD +{ + if (socketFD != SOCKET_NULL && + (socketFD == socket6FD || socketFD == socket4FD)) + { + close(socketFD); + + if (socketFD == socket4FD) + { + LogVerbose(@"close(socket4FD)"); + socket4FD = SOCKET_NULL; + } + else if (socketFD == socket6FD) + { + LogVerbose(@"close(socket6FD)"); + socket6FD = SOCKET_NULL; + } + } +} + +- (void)closeUnusedSocket:(int)usedSocketFD +{ + if (usedSocketFD != socket4FD) + { + [self closeSocket:socket4FD]; + } + else if (usedSocketFD != socket6FD) + { + [self closeSocket:socket6FD]; + } +} + +- (BOOL)connectWithAddress4:(NSData *)address4 address6:(NSData *)address6 error:(NSError **)errPtr +{ + LogTrace(); + + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + + LogVerbose(@"IPv4: %@:%hu", [[self class] hostFromAddress:address4], [[self class] portFromAddress:address4]); + LogVerbose(@"IPv6: %@:%hu", [[self class] hostFromAddress:address6], [[self class] portFromAddress:address6]); + + // Determine socket type + + BOOL preferIPv6 = (config & kPreferIPv6) ? YES : NO; + + // Create and bind the sockets + + if (address4) + { + LogVerbose(@"Creating IPv4 socket"); + + socket4FD = [self createSocket:AF_INET connectInterface:connectInterface4 errPtr:errPtr]; + } + + if (address6) + { + LogVerbose(@"Creating IPv6 socket"); + + socket6FD = [self createSocket:AF_INET6 connectInterface:connectInterface6 errPtr:errPtr]; + } + + if (socket4FD == SOCKET_NULL && socket6FD == SOCKET_NULL) + { + return NO; + } + + int socketFD, alternateSocketFD; + NSData *address, *alternateAddress; + + if ((preferIPv6 && socket6FD) || socket4FD == SOCKET_NULL) + { + socketFD = socket6FD; + alternateSocketFD = socket4FD; + address = address6; + alternateAddress = address4; + } + else + { + socketFD = socket4FD; + alternateSocketFD = socket6FD; + address = address4; + alternateAddress = address6; + } + + int aStateIndex = stateIndex; + + [self connectSocket:socketFD address:address stateIndex:aStateIndex]; + + if (alternateAddress) + { + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(alternateAddressDelay * NSEC_PER_SEC)), socketQueue, ^{ + [self connectSocket:alternateSocketFD address:alternateAddress stateIndex:aStateIndex]; + }); + } + + return YES; +} + +- (BOOL)connectWithAddressUN:(NSData *)address error:(NSError **)errPtr +{ + LogTrace(); + + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + + // Create the socket + + int socketFD; + + LogVerbose(@"Creating unix domain socket"); + + socketUN = socket(AF_UNIX, SOCK_STREAM, 0); + + socketFD = socketUN; + + if (socketFD == SOCKET_NULL) + { + if (errPtr) + *errPtr = [self errnoErrorWithReason:@"Error in socket() function"]; + + return NO; + } + + // Bind the socket to the desired interface (if needed) + + LogVerbose(@"Binding socket..."); + + int reuseOn = 1; + setsockopt(socketFD, SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn)); + +// const struct sockaddr *interfaceAddr = (const struct sockaddr *)[address bytes]; +// +// int result = bind(socketFD, interfaceAddr, (socklen_t)[address length]); +// if (result != 0) +// { +// if (errPtr) +// *errPtr = [self errnoErrorWithReason:@"Error in bind() function"]; +// +// return NO; +// } + + // Prevent SIGPIPE signals + + int nosigpipe = 1; + setsockopt(socketFD, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe)); + + // Start the connection process in a background queue + + int aStateIndex = stateIndex; + + dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); + dispatch_async(globalConcurrentQueue, ^{ + + const struct sockaddr *addr = (const struct sockaddr *)[address bytes]; + int result = connect(socketFD, addr, addr->sa_len); + if (result == 0) + { + dispatch_async(socketQueue, ^{ @autoreleasepool { + + [self didConnect:aStateIndex]; + }}); + } + else + { + // TODO: Bad file descriptor + perror("connect"); + NSError *error = [self errnoErrorWithReason:@"Error in connect() function"]; + + dispatch_async(socketQueue, ^{ @autoreleasepool { + + [self didNotConnect:aStateIndex error:error]; + }}); + } + }); + + LogVerbose(@"Connecting..."); + + return YES; +} + +- (void)didConnect:(int)aStateIndex +{ + LogTrace(); + + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + + + if (aStateIndex != stateIndex) + { + LogInfo(@"Ignoring didConnect, already disconnected"); + + // The connect operation has been cancelled. + // That is, socket was disconnected, or connection has already timed out. + return; + } + + flags |= kConnected; + + [self endConnectTimeout]; + + #if TARGET_OS_IPHONE + // The endConnectTimeout method executed above incremented the stateIndex. + aStateIndex = stateIndex; + #endif + + // Setup read/write streams (as workaround for specific shortcomings in the iOS platform) + // + // Note: + // There may be configuration options that must be set by the delegate before opening the streams. + // The primary example is the kCFStreamNetworkServiceTypeVoIP flag, which only works on an unopened stream. + // + // Thus we wait until after the socket:didConnectToHost:port: delegate method has completed. + // This gives the delegate time to properly configure the streams if needed. + + dispatch_block_t SetupStreamsPart1 = ^{ + #if TARGET_OS_IPHONE + + if (![self createReadAndWriteStream]) + { + [self closeWithError:[self otherError:@"Error creating CFStreams"]]; + return; + } + + if (![self registerForStreamCallbacksIncludingReadWrite:NO]) + { + [self closeWithError:[self otherError:@"Error in CFStreamSetClient"]]; + return; + } + + #endif + }; + dispatch_block_t SetupStreamsPart2 = ^{ + #if TARGET_OS_IPHONE + + if (aStateIndex != stateIndex) + { + // The socket has been disconnected. + return; + } + + if (![self addStreamsToRunLoop]) + { + [self closeWithError:[self otherError:@"Error in CFStreamScheduleWithRunLoop"]]; + return; + } + + if (![self openStreams]) + { + [self closeWithError:[self otherError:@"Error creating CFStreams"]]; + return; + } + + #endif + }; + + // Notify delegate + + NSString *host = [self connectedHost]; + uint16_t port = [self connectedPort]; + NSURL *url = [self connectedUrl]; + + __strong id theDelegate = delegate; + + if (delegateQueue && host != nil && [theDelegate respondsToSelector:@selector(socket:didConnectToHost:port:)]) + { + SetupStreamsPart1(); + + dispatch_async(delegateQueue, ^{ @autoreleasepool { + + [theDelegate socket:self didConnectToHost:host port:port]; + + dispatch_async(socketQueue, ^{ @autoreleasepool { + + SetupStreamsPart2(); + }}); + }}); + } + else if (delegateQueue && url != nil && [theDelegate respondsToSelector:@selector(socket:didConnectToUrl:)]) + { + SetupStreamsPart1(); + + dispatch_async(delegateQueue, ^{ @autoreleasepool { + + [theDelegate socket:self didConnectToUrl:url]; + + dispatch_async(socketQueue, ^{ @autoreleasepool { + + SetupStreamsPart2(); + }}); + }}); + } + else + { + SetupStreamsPart1(); + SetupStreamsPart2(); + } + + // Get the connected socket + + int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN; + + // Enable non-blocking IO on the socket + + int result = fcntl(socketFD, F_SETFL, O_NONBLOCK); + if (result == -1) + { + NSString *errMsg = @"Error enabling non-blocking IO on socket (fcntl)"; + [self closeWithError:[self otherError:errMsg]]; + + return; + } + + // Setup our read/write sources + + [self setupReadAndWriteSourcesForNewlyConnectedSocket:socketFD]; + + // Dequeue any pending read/write requests + + [self maybeDequeueRead]; + [self maybeDequeueWrite]; +} + +- (void)didNotConnect:(int)aStateIndex error:(NSError *)error +{ + LogTrace(); + + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + + + if (aStateIndex != stateIndex) + { + LogInfo(@"Ignoring didNotConnect, already disconnected"); + + // The connect operation has been cancelled. + // That is, socket was disconnected, or connection has already timed out. + return; + } + + [self closeWithError:error]; +} + +- (void)startConnectTimeout:(NSTimeInterval)timeout +{ + if (timeout >= 0.0) + { + connectTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, socketQueue); + + __weak GCDAsyncSocket *weakSelf = self; + + dispatch_source_set_event_handler(connectTimer, ^{ @autoreleasepool { + #pragma clang diagnostic push + #pragma clang diagnostic warning "-Wimplicit-retain-self" + + __strong GCDAsyncSocket *strongSelf = weakSelf; + if (strongSelf == nil) return_from_block; + + [strongSelf doConnectTimeout]; + + #pragma clang diagnostic pop + }}); + + #if !OS_OBJECT_USE_OBJC + dispatch_source_t theConnectTimer = connectTimer; + dispatch_source_set_cancel_handler(connectTimer, ^{ + #pragma clang diagnostic push + #pragma clang diagnostic warning "-Wimplicit-retain-self" + + LogVerbose(@"dispatch_release(connectTimer)"); + dispatch_release(theConnectTimer); + + #pragma clang diagnostic pop + }); + #endif + + dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC)); + dispatch_source_set_timer(connectTimer, tt, DISPATCH_TIME_FOREVER, 0); + + dispatch_resume(connectTimer); + } +} + +- (void)endConnectTimeout +{ + LogTrace(); + + if (connectTimer) + { + dispatch_source_cancel(connectTimer); + connectTimer = NULL; + } + + // Increment stateIndex. + // This will prevent us from processing results from any related background asynchronous operations. + // + // Note: This should be called from close method even if connectTimer is NULL. + // This is because one might disconnect a socket prior to a successful connection which had no timeout. + + stateIndex++; + + if (connectInterface4) + { + connectInterface4 = nil; + } + if (connectInterface6) + { + connectInterface6 = nil; + } +} + +- (void)doConnectTimeout +{ + LogTrace(); + + [self endConnectTimeout]; + [self closeWithError:[self connectTimeoutError]]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Disconnecting +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)closeWithError:(NSError *)error +{ + LogTrace(); + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + + [self endConnectTimeout]; + + if (currentRead != nil) [self endCurrentRead]; + if (currentWrite != nil) [self endCurrentWrite]; + + [readQueue removeAllObjects]; + [writeQueue removeAllObjects]; + + [preBuffer reset]; + + #if TARGET_OS_IPHONE + { + if (readStream || writeStream) + { + [self removeStreamsFromRunLoop]; + + if (readStream) + { + CFReadStreamSetClient(readStream, kCFStreamEventNone, NULL, NULL); + CFReadStreamClose(readStream); + CFRelease(readStream); + readStream = NULL; + } + if (writeStream) + { + CFWriteStreamSetClient(writeStream, kCFStreamEventNone, NULL, NULL); + CFWriteStreamClose(writeStream); + CFRelease(writeStream); + writeStream = NULL; + } + } + } + #endif + + [sslPreBuffer reset]; + sslErrCode = lastSSLHandshakeError = noErr; + + if (sslContext) + { + // Getting a linker error here about the SSLx() functions? + // You need to add the Security Framework to your application. + + SSLClose(sslContext); + + #if TARGET_OS_IPHONE || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) + CFRelease(sslContext); + #else + SSLDisposeContext(sslContext); + #endif + + sslContext = NULL; + } + + // For some crazy reason (in my opinion), cancelling a dispatch source doesn't + // invoke the cancel handler if the dispatch source is paused. + // So we have to unpause the source if needed. + // This allows the cancel handler to be run, which in turn releases the source and closes the socket. + + if (!accept4Source && !accept6Source && !acceptUNSource && !readSource && !writeSource) + { + LogVerbose(@"manually closing close"); + + if (socket4FD != SOCKET_NULL) + { + LogVerbose(@"close(socket4FD)"); + close(socket4FD); + socket4FD = SOCKET_NULL; + } + + if (socket6FD != SOCKET_NULL) + { + LogVerbose(@"close(socket6FD)"); + close(socket6FD); + socket6FD = SOCKET_NULL; + } + + if (socketUN != SOCKET_NULL) + { + LogVerbose(@"close(socketUN)"); + close(socketUN); + socketUN = SOCKET_NULL; + unlink(socketUrl.path.fileSystemRepresentation); + socketUrl = nil; + } + } + else + { + if (accept4Source) + { + LogVerbose(@"dispatch_source_cancel(accept4Source)"); + dispatch_source_cancel(accept4Source); + + // We never suspend accept4Source + + accept4Source = NULL; + } + + if (accept6Source) + { + LogVerbose(@"dispatch_source_cancel(accept6Source)"); + dispatch_source_cancel(accept6Source); + + // We never suspend accept6Source + + accept6Source = NULL; + } + + if (acceptUNSource) + { + LogVerbose(@"dispatch_source_cancel(acceptUNSource)"); + dispatch_source_cancel(acceptUNSource); + + // We never suspend acceptUNSource + + acceptUNSource = NULL; + } + + if (readSource) + { + LogVerbose(@"dispatch_source_cancel(readSource)"); + dispatch_source_cancel(readSource); + + [self resumeReadSource]; + + readSource = NULL; + } + + if (writeSource) + { + LogVerbose(@"dispatch_source_cancel(writeSource)"); + dispatch_source_cancel(writeSource); + + [self resumeWriteSource]; + + writeSource = NULL; + } + + // The sockets will be closed by the cancel handlers of the corresponding source + + socket4FD = SOCKET_NULL; + socket6FD = SOCKET_NULL; + socketUN = SOCKET_NULL; + } + + // If the client has passed the connect/accept method, then the connection has at least begun. + // Notify delegate that it is now ending. + BOOL shouldCallDelegate = (flags & kSocketStarted) ? YES : NO; + BOOL isDeallocating = (flags & kDealloc) ? YES : NO; + + // Clear stored socket info and all flags (config remains as is) + socketFDBytesAvailable = 0; + flags = 0; + sslWriteCachedLength = 0; + + if (shouldCallDelegate) + { + __strong id theDelegate = delegate; + __strong id theSelf = isDeallocating ? nil : self; + + if (delegateQueue && [theDelegate respondsToSelector: @selector(socketDidDisconnect:withError:)]) + { + dispatch_async(delegateQueue, ^{ @autoreleasepool { + + [theDelegate socketDidDisconnect:theSelf withError:error]; + }}); + } + } +} + +- (void)disconnect +{ + dispatch_block_t block = ^{ @autoreleasepool { + + if (flags & kSocketStarted) + { + [self closeWithError:nil]; + } + }}; + + // Synchronous disconnection, as documented in the header file + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); +} + +- (void)disconnectAfterReading +{ + dispatch_async(socketQueue, ^{ @autoreleasepool { + + if (flags & kSocketStarted) + { + flags |= (kForbidReadsWrites | kDisconnectAfterReads); + [self maybeClose]; + } + }}); +} + +- (void)disconnectAfterWriting +{ + dispatch_async(socketQueue, ^{ @autoreleasepool { + + if (flags & kSocketStarted) + { + flags |= (kForbidReadsWrites | kDisconnectAfterWrites); + [self maybeClose]; + } + }}); +} + +- (void)disconnectAfterReadingAndWriting +{ + dispatch_async(socketQueue, ^{ @autoreleasepool { + + if (flags & kSocketStarted) + { + flags |= (kForbidReadsWrites | kDisconnectAfterReads | kDisconnectAfterWrites); + [self maybeClose]; + } + }}); +} + +/** + * Closes the socket if possible. + * That is, if all writes have completed, and we're set to disconnect after writing, + * or if all reads have completed, and we're set to disconnect after reading. +**/ +- (void)maybeClose +{ + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + + BOOL shouldClose = NO; + + if (flags & kDisconnectAfterReads) + { + if (([readQueue count] == 0) && (currentRead == nil)) + { + if (flags & kDisconnectAfterWrites) + { + if (([writeQueue count] == 0) && (currentWrite == nil)) + { + shouldClose = YES; + } + } + else + { + shouldClose = YES; + } + } + } + else if (flags & kDisconnectAfterWrites) + { + if (([writeQueue count] == 0) && (currentWrite == nil)) + { + shouldClose = YES; + } + } + + if (shouldClose) + { + [self closeWithError:nil]; + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Errors +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (NSError *)badConfigError:(NSString *)errMsg +{ + NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + + return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketBadConfigError userInfo:userInfo]; +} + +- (NSError *)badParamError:(NSString *)errMsg +{ + NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + + return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketBadParamError userInfo:userInfo]; +} + ++ (NSError *)gaiError:(int)gai_error +{ + NSString *errMsg = [NSString stringWithCString:gai_strerror(gai_error) encoding:NSASCIIStringEncoding]; + NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + + return [NSError errorWithDomain:@"kCFStreamErrorDomainNetDB" code:gai_error userInfo:userInfo]; +} + +- (NSError *)errnoErrorWithReason:(NSString *)reason +{ + NSString *errMsg = [NSString stringWithUTF8String:strerror(errno)]; + NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:errMsg, NSLocalizedDescriptionKey, + reason, NSLocalizedFailureReasonErrorKey, nil]; + + return [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:userInfo]; +} + +- (NSError *)errnoError +{ + NSString *errMsg = [NSString stringWithUTF8String:strerror(errno)]; + NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + + return [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:userInfo]; +} + +- (NSError *)sslError:(OSStatus)ssl_error +{ + NSString *msg = @"Error code definition can be found in Apple's SecureTransport.h"; + NSDictionary *userInfo = [NSDictionary dictionaryWithObject:msg forKey:NSLocalizedRecoverySuggestionErrorKey]; + + return [NSError errorWithDomain:@"kCFStreamErrorDomainSSL" code:ssl_error userInfo:userInfo]; +} + +- (NSError *)connectTimeoutError +{ + NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketConnectTimeoutError", + @"GCDAsyncSocket", [NSBundle mainBundle], + @"Attempt to connect to host timed out", nil); + + NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + + return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketConnectTimeoutError userInfo:userInfo]; +} + +/** + * Returns a standard AsyncSocket maxed out error. +**/ +- (NSError *)readMaxedOutError +{ + NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketReadMaxedOutError", + @"GCDAsyncSocket", [NSBundle mainBundle], + @"Read operation reached set maximum length", nil); + + NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + + return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketReadMaxedOutError userInfo:info]; +} + +/** + * Returns a standard AsyncSocket write timeout error. +**/ +- (NSError *)readTimeoutError +{ + NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketReadTimeoutError", + @"GCDAsyncSocket", [NSBundle mainBundle], + @"Read operation timed out", nil); + + NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + + return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketReadTimeoutError userInfo:userInfo]; +} + +/** + * Returns a standard AsyncSocket write timeout error. +**/ +- (NSError *)writeTimeoutError +{ + NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketWriteTimeoutError", + @"GCDAsyncSocket", [NSBundle mainBundle], + @"Write operation timed out", nil); + + NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + + return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketWriteTimeoutError userInfo:userInfo]; +} + +- (NSError *)connectionClosedError +{ + NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketClosedError", + @"GCDAsyncSocket", [NSBundle mainBundle], + @"Socket closed by remote peer", nil); + + NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + + return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketClosedError userInfo:userInfo]; +} + +- (NSError *)otherError:(NSString *)errMsg +{ + NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + + return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketOtherError userInfo:userInfo]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Diagnostics +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (BOOL)isDisconnected +{ + __block BOOL result = NO; + + dispatch_block_t block = ^{ + result = (flags & kSocketStarted) ? NO : YES; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + return result; +} + +- (BOOL)isConnected +{ + __block BOOL result = NO; + + dispatch_block_t block = ^{ + result = (flags & kConnected) ? YES : NO; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + return result; +} + +- (NSString *)connectedHost +{ + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + if (socket4FD != SOCKET_NULL) + return [self connectedHostFromSocket4:socket4FD]; + if (socket6FD != SOCKET_NULL) + return [self connectedHostFromSocket6:socket6FD]; + + return nil; + } + else + { + __block NSString *result = nil; + + dispatch_sync(socketQueue, ^{ @autoreleasepool { + + if (socket4FD != SOCKET_NULL) + result = [self connectedHostFromSocket4:socket4FD]; + else if (socket6FD != SOCKET_NULL) + result = [self connectedHostFromSocket6:socket6FD]; + }}); + + return result; + } +} + +- (uint16_t)connectedPort +{ + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + if (socket4FD != SOCKET_NULL) + return [self connectedPortFromSocket4:socket4FD]; + if (socket6FD != SOCKET_NULL) + return [self connectedPortFromSocket6:socket6FD]; + + return 0; + } + else + { + __block uint16_t result = 0; + + dispatch_sync(socketQueue, ^{ + // No need for autorelease pool + + if (socket4FD != SOCKET_NULL) + result = [self connectedPortFromSocket4:socket4FD]; + else if (socket6FD != SOCKET_NULL) + result = [self connectedPortFromSocket6:socket6FD]; + }); + + return result; + } +} + +- (NSURL *)connectedUrl +{ + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + if (socketUN != SOCKET_NULL) + return [self connectedUrlFromSocketUN:socketUN]; + + return nil; + } + else + { + __block NSURL *result = nil; + + dispatch_sync(socketQueue, ^{ @autoreleasepool { + + if (socketUN != SOCKET_NULL) + result = [self connectedUrlFromSocketUN:socketUN]; + }}); + + return result; + } +} + +- (NSString *)localHost +{ + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + if (socket4FD != SOCKET_NULL) + return [self localHostFromSocket4:socket4FD]; + if (socket6FD != SOCKET_NULL) + return [self localHostFromSocket6:socket6FD]; + + return nil; + } + else + { + __block NSString *result = nil; + + dispatch_sync(socketQueue, ^{ @autoreleasepool { + + if (socket4FD != SOCKET_NULL) + result = [self localHostFromSocket4:socket4FD]; + else if (socket6FD != SOCKET_NULL) + result = [self localHostFromSocket6:socket6FD]; + }}); + + return result; + } +} + +- (uint16_t)localPort +{ + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + if (socket4FD != SOCKET_NULL) + return [self localPortFromSocket4:socket4FD]; + if (socket6FD != SOCKET_NULL) + return [self localPortFromSocket6:socket6FD]; + + return 0; + } + else + { + __block uint16_t result = 0; + + dispatch_sync(socketQueue, ^{ + // No need for autorelease pool + + if (socket4FD != SOCKET_NULL) + result = [self localPortFromSocket4:socket4FD]; + else if (socket6FD != SOCKET_NULL) + result = [self localPortFromSocket6:socket6FD]; + }); + + return result; + } +} + +- (NSString *)connectedHost4 +{ + if (socket4FD != SOCKET_NULL) + return [self connectedHostFromSocket4:socket4FD]; + + return nil; +} + +- (NSString *)connectedHost6 +{ + if (socket6FD != SOCKET_NULL) + return [self connectedHostFromSocket6:socket6FD]; + + return nil; +} + +- (uint16_t)connectedPort4 +{ + if (socket4FD != SOCKET_NULL) + return [self connectedPortFromSocket4:socket4FD]; + + return 0; +} + +- (uint16_t)connectedPort6 +{ + if (socket6FD != SOCKET_NULL) + return [self connectedPortFromSocket6:socket6FD]; + + return 0; +} + +- (NSString *)localHost4 +{ + if (socket4FD != SOCKET_NULL) + return [self localHostFromSocket4:socket4FD]; + + return nil; +} + +- (NSString *)localHost6 +{ + if (socket6FD != SOCKET_NULL) + return [self localHostFromSocket6:socket6FD]; + + return nil; +} + +- (uint16_t)localPort4 +{ + if (socket4FD != SOCKET_NULL) + return [self localPortFromSocket4:socket4FD]; + + return 0; +} + +- (uint16_t)localPort6 +{ + if (socket6FD != SOCKET_NULL) + return [self localPortFromSocket6:socket6FD]; + + return 0; +} + +- (NSString *)connectedHostFromSocket4:(int)socketFD +{ + struct sockaddr_in sockaddr4; + socklen_t sockaddr4len = sizeof(sockaddr4); + + if (getpeername(socketFD, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0) + { + return nil; + } + return [[self class] hostFromSockaddr4:&sockaddr4]; +} + +- (NSString *)connectedHostFromSocket6:(int)socketFD +{ + struct sockaddr_in6 sockaddr6; + socklen_t sockaddr6len = sizeof(sockaddr6); + + if (getpeername(socketFD, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0) + { + return nil; + } + return [[self class] hostFromSockaddr6:&sockaddr6]; +} + +- (uint16_t)connectedPortFromSocket4:(int)socketFD +{ + struct sockaddr_in sockaddr4; + socklen_t sockaddr4len = sizeof(sockaddr4); + + if (getpeername(socketFD, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0) + { + return 0; + } + return [[self class] portFromSockaddr4:&sockaddr4]; +} + +- (uint16_t)connectedPortFromSocket6:(int)socketFD +{ + struct sockaddr_in6 sockaddr6; + socklen_t sockaddr6len = sizeof(sockaddr6); + + if (getpeername(socketFD, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0) + { + return 0; + } + return [[self class] portFromSockaddr6:&sockaddr6]; +} + +- (NSURL *)connectedUrlFromSocketUN:(int)socketFD +{ + struct sockaddr_un sockaddr; + socklen_t sockaddrlen = sizeof(sockaddr); + + if (getpeername(socketFD, (struct sockaddr *)&sockaddr, &sockaddrlen) < 0) + { + return 0; + } + return [[self class] urlFromSockaddrUN:&sockaddr]; +} + +- (NSString *)localHostFromSocket4:(int)socketFD +{ + struct sockaddr_in sockaddr4; + socklen_t sockaddr4len = sizeof(sockaddr4); + + if (getsockname(socketFD, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0) + { + return nil; + } + return [[self class] hostFromSockaddr4:&sockaddr4]; +} + +- (NSString *)localHostFromSocket6:(int)socketFD +{ + struct sockaddr_in6 sockaddr6; + socklen_t sockaddr6len = sizeof(sockaddr6); + + if (getsockname(socketFD, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0) + { + return nil; + } + return [[self class] hostFromSockaddr6:&sockaddr6]; +} + +- (uint16_t)localPortFromSocket4:(int)socketFD +{ + struct sockaddr_in sockaddr4; + socklen_t sockaddr4len = sizeof(sockaddr4); + + if (getsockname(socketFD, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0) + { + return 0; + } + return [[self class] portFromSockaddr4:&sockaddr4]; +} + +- (uint16_t)localPortFromSocket6:(int)socketFD +{ + struct sockaddr_in6 sockaddr6; + socklen_t sockaddr6len = sizeof(sockaddr6); + + if (getsockname(socketFD, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0) + { + return 0; + } + return [[self class] portFromSockaddr6:&sockaddr6]; +} + +- (NSData *)connectedAddress +{ + __block NSData *result = nil; + + dispatch_block_t block = ^{ + if (socket4FD != SOCKET_NULL) + { + struct sockaddr_in sockaddr4; + socklen_t sockaddr4len = sizeof(sockaddr4); + + if (getpeername(socket4FD, (struct sockaddr *)&sockaddr4, &sockaddr4len) == 0) + { + result = [[NSData alloc] initWithBytes:&sockaddr4 length:sockaddr4len]; + } + } + + if (socket6FD != SOCKET_NULL) + { + struct sockaddr_in6 sockaddr6; + socklen_t sockaddr6len = sizeof(sockaddr6); + + if (getpeername(socket6FD, (struct sockaddr *)&sockaddr6, &sockaddr6len) == 0) + { + result = [[NSData alloc] initWithBytes:&sockaddr6 length:sockaddr6len]; + } + } + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + return result; +} + +- (NSData *)localAddress +{ + __block NSData *result = nil; + + dispatch_block_t block = ^{ + if (socket4FD != SOCKET_NULL) + { + struct sockaddr_in sockaddr4; + socklen_t sockaddr4len = sizeof(sockaddr4); + + if (getsockname(socket4FD, (struct sockaddr *)&sockaddr4, &sockaddr4len) == 0) + { + result = [[NSData alloc] initWithBytes:&sockaddr4 length:sockaddr4len]; + } + } + + if (socket6FD != SOCKET_NULL) + { + struct sockaddr_in6 sockaddr6; + socklen_t sockaddr6len = sizeof(sockaddr6); + + if (getsockname(socket6FD, (struct sockaddr *)&sockaddr6, &sockaddr6len) == 0) + { + result = [[NSData alloc] initWithBytes:&sockaddr6 length:sockaddr6len]; + } + } + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + return result; +} + +- (BOOL)isIPv4 +{ + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + return (socket4FD != SOCKET_NULL); + } + else + { + __block BOOL result = NO; + + dispatch_sync(socketQueue, ^{ + result = (socket4FD != SOCKET_NULL); + }); + + return result; + } +} + +- (BOOL)isIPv6 +{ + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + return (socket6FD != SOCKET_NULL); + } + else + { + __block BOOL result = NO; + + dispatch_sync(socketQueue, ^{ + result = (socket6FD != SOCKET_NULL); + }); + + return result; + } +} + +- (BOOL)isSecure +{ + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + return (flags & kSocketSecure) ? YES : NO; + } + else + { + __block BOOL result; + + dispatch_sync(socketQueue, ^{ + result = (flags & kSocketSecure) ? YES : NO; + }); + + return result; + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Utilities +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * Finds the address of an interface description. + * An inteface description may be an interface name (en0, en1, lo0) or corresponding IP (192.168.4.34). + * + * The interface description may optionally contain a port number at the end, separated by a colon. + * If a non-zero port parameter is provided, any port number in the interface description is ignored. + * + * The returned value is a 'struct sockaddr' wrapped in an NSMutableData object. +**/ +- (void)getInterfaceAddress4:(NSMutableData **)interfaceAddr4Ptr + address6:(NSMutableData **)interfaceAddr6Ptr + fromDescription:(NSString *)interfaceDescription + port:(uint16_t)port +{ + NSMutableData *addr4 = nil; + NSMutableData *addr6 = nil; + + NSString *interface = nil; + + NSArray *components = [interfaceDescription componentsSeparatedByString:@":"]; + if ([components count] > 0) + { + NSString *temp = [components objectAtIndex:0]; + if ([temp length] > 0) + { + interface = temp; + } + } + if ([components count] > 1 && port == 0) + { + long portL = strtol([[components objectAtIndex:1] UTF8String], NULL, 10); + + if (portL > 0 && portL <= UINT16_MAX) + { + port = (uint16_t)portL; + } + } + + if (interface == nil) + { + // ANY address + + struct sockaddr_in sockaddr4; + memset(&sockaddr4, 0, sizeof(sockaddr4)); + + sockaddr4.sin_len = sizeof(sockaddr4); + sockaddr4.sin_family = AF_INET; + sockaddr4.sin_port = htons(port); + sockaddr4.sin_addr.s_addr = htonl(INADDR_ANY); + + struct sockaddr_in6 sockaddr6; + memset(&sockaddr6, 0, sizeof(sockaddr6)); + + sockaddr6.sin6_len = sizeof(sockaddr6); + sockaddr6.sin6_family = AF_INET6; + sockaddr6.sin6_port = htons(port); + sockaddr6.sin6_addr = in6addr_any; + + addr4 = [NSMutableData dataWithBytes:&sockaddr4 length:sizeof(sockaddr4)]; + addr6 = [NSMutableData dataWithBytes:&sockaddr6 length:sizeof(sockaddr6)]; + } + else if ([interface isEqualToString:@"localhost"] || [interface isEqualToString:@"loopback"]) + { + // LOOPBACK address + + struct sockaddr_in sockaddr4; + memset(&sockaddr4, 0, sizeof(sockaddr4)); + + sockaddr4.sin_len = sizeof(sockaddr4); + sockaddr4.sin_family = AF_INET; + sockaddr4.sin_port = htons(port); + sockaddr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + struct sockaddr_in6 sockaddr6; + memset(&sockaddr6, 0, sizeof(sockaddr6)); + + sockaddr6.sin6_len = sizeof(sockaddr6); + sockaddr6.sin6_family = AF_INET6; + sockaddr6.sin6_port = htons(port); + sockaddr6.sin6_addr = in6addr_loopback; + + addr4 = [NSMutableData dataWithBytes:&sockaddr4 length:sizeof(sockaddr4)]; + addr6 = [NSMutableData dataWithBytes:&sockaddr6 length:sizeof(sockaddr6)]; + } + else + { + const char *iface = [interface UTF8String]; + + struct ifaddrs *addrs; + const struct ifaddrs *cursor; + + if ((getifaddrs(&addrs) == 0)) + { + cursor = addrs; + while (cursor != NULL) + { + if ((addr4 == nil) && (cursor->ifa_addr->sa_family == AF_INET)) + { + // IPv4 + + struct sockaddr_in nativeAddr4; + memcpy(&nativeAddr4, cursor->ifa_addr, sizeof(nativeAddr4)); + + if (strcmp(cursor->ifa_name, iface) == 0) + { + // Name match + + nativeAddr4.sin_port = htons(port); + + addr4 = [NSMutableData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)]; + } + else + { + char ip[INET_ADDRSTRLEN]; + + const char *conversion = inet_ntop(AF_INET, &nativeAddr4.sin_addr, ip, sizeof(ip)); + + if ((conversion != NULL) && (strcmp(ip, iface) == 0)) + { + // IP match + + nativeAddr4.sin_port = htons(port); + + addr4 = [NSMutableData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)]; + } + } + } + else if ((addr6 == nil) && (cursor->ifa_addr->sa_family == AF_INET6)) + { + // IPv6 + + struct sockaddr_in6 nativeAddr6; + memcpy(&nativeAddr6, cursor->ifa_addr, sizeof(nativeAddr6)); + + if (strcmp(cursor->ifa_name, iface) == 0) + { + // Name match + + nativeAddr6.sin6_port = htons(port); + + addr6 = [NSMutableData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)]; + } + else + { + char ip[INET6_ADDRSTRLEN]; + + const char *conversion = inet_ntop(AF_INET6, &nativeAddr6.sin6_addr, ip, sizeof(ip)); + + if ((conversion != NULL) && (strcmp(ip, iface) == 0)) + { + // IP match + + nativeAddr6.sin6_port = htons(port); + + addr6 = [NSMutableData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)]; + } + } + } + + cursor = cursor->ifa_next; + } + + freeifaddrs(addrs); + } + } + + if (interfaceAddr4Ptr) *interfaceAddr4Ptr = addr4; + if (interfaceAddr6Ptr) *interfaceAddr6Ptr = addr6; +} + +- (NSData *)getInterfaceAddressFromUrl:(NSURL *)url; +{ + NSString *path = url.path; + if (path.length == 0) { + return nil; + } + + struct sockaddr_un nativeAddr; + nativeAddr.sun_family = AF_UNIX; + strlcpy(nativeAddr.sun_path, path.fileSystemRepresentation, sizeof(nativeAddr.sun_path)); + nativeAddr.sun_len = SUN_LEN(&nativeAddr); + NSData *interface = [NSData dataWithBytes:&nativeAddr length:sizeof(struct sockaddr_un)]; + + return interface; +} + +- (void)setupReadAndWriteSourcesForNewlyConnectedSocket:(int)socketFD +{ + readSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, socketFD, 0, socketQueue); + writeSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE, socketFD, 0, socketQueue); + + // Setup event handlers + + __weak GCDAsyncSocket *weakSelf = self; + + dispatch_source_set_event_handler(readSource, ^{ @autoreleasepool { + #pragma clang diagnostic push + #pragma clang diagnostic warning "-Wimplicit-retain-self" + + __strong GCDAsyncSocket *strongSelf = weakSelf; + if (strongSelf == nil) return_from_block; + + LogVerbose(@"readEventBlock"); + + strongSelf->socketFDBytesAvailable = dispatch_source_get_data(strongSelf->readSource); + LogVerbose(@"socketFDBytesAvailable: %lu", strongSelf->socketFDBytesAvailable); + + if (strongSelf->socketFDBytesAvailable > 0) + [strongSelf doReadData]; + else + [strongSelf doReadEOF]; + + #pragma clang diagnostic pop + }}); + + dispatch_source_set_event_handler(writeSource, ^{ @autoreleasepool { + #pragma clang diagnostic push + #pragma clang diagnostic warning "-Wimplicit-retain-self" + + __strong GCDAsyncSocket *strongSelf = weakSelf; + if (strongSelf == nil) return_from_block; + + LogVerbose(@"writeEventBlock"); + + strongSelf->flags |= kSocketCanAcceptBytes; + [strongSelf doWriteData]; + + #pragma clang diagnostic pop + }}); + + // Setup cancel handlers + + __block int socketFDRefCount = 2; + + #if !OS_OBJECT_USE_OBJC + dispatch_source_t theReadSource = readSource; + dispatch_source_t theWriteSource = writeSource; + #endif + + dispatch_source_set_cancel_handler(readSource, ^{ + #pragma clang diagnostic push + #pragma clang diagnostic warning "-Wimplicit-retain-self" + + LogVerbose(@"readCancelBlock"); + + #if !OS_OBJECT_USE_OBJC + LogVerbose(@"dispatch_release(readSource)"); + dispatch_release(theReadSource); + #endif + + if (--socketFDRefCount == 0) + { + LogVerbose(@"close(socketFD)"); + close(socketFD); + } + + #pragma clang diagnostic pop + }); + + dispatch_source_set_cancel_handler(writeSource, ^{ + #pragma clang diagnostic push + #pragma clang diagnostic warning "-Wimplicit-retain-self" + + LogVerbose(@"writeCancelBlock"); + + #if !OS_OBJECT_USE_OBJC + LogVerbose(@"dispatch_release(writeSource)"); + dispatch_release(theWriteSource); + #endif + + if (--socketFDRefCount == 0) + { + LogVerbose(@"close(socketFD)"); + close(socketFD); + } + + #pragma clang diagnostic pop + }); + + // We will not be able to read until data arrives. + // But we should be able to write immediately. + + socketFDBytesAvailable = 0; + flags &= ~kReadSourceSuspended; + + LogVerbose(@"dispatch_resume(readSource)"); + dispatch_resume(readSource); + + flags |= kSocketCanAcceptBytes; + flags |= kWriteSourceSuspended; +} + +- (BOOL)usingCFStreamForTLS +{ + #if TARGET_OS_IPHONE + + if ((flags & kSocketSecure) && (flags & kUsingCFStreamForTLS)) + { + // The startTLS method was given the GCDAsyncSocketUseCFStreamForTLS flag. + + return YES; + } + + #endif + + return NO; +} + +- (BOOL)usingSecureTransportForTLS +{ + // Invoking this method is equivalent to ![self usingCFStreamForTLS] (just more readable) + + #if TARGET_OS_IPHONE + + if ((flags & kSocketSecure) && (flags & kUsingCFStreamForTLS)) + { + // The startTLS method was given the GCDAsyncSocketUseCFStreamForTLS flag. + + return NO; + } + + #endif + + return YES; +} + +- (void)suspendReadSource +{ + if (!(flags & kReadSourceSuspended)) + { + LogVerbose(@"dispatch_suspend(readSource)"); + + dispatch_suspend(readSource); + flags |= kReadSourceSuspended; + } +} + +- (void)resumeReadSource +{ + if (flags & kReadSourceSuspended) + { + LogVerbose(@"dispatch_resume(readSource)"); + + dispatch_resume(readSource); + flags &= ~kReadSourceSuspended; + } +} + +- (void)suspendWriteSource +{ + if (!(flags & kWriteSourceSuspended)) + { + LogVerbose(@"dispatch_suspend(writeSource)"); + + dispatch_suspend(writeSource); + flags |= kWriteSourceSuspended; + } +} + +- (void)resumeWriteSource +{ + if (flags & kWriteSourceSuspended) + { + LogVerbose(@"dispatch_resume(writeSource)"); + + dispatch_resume(writeSource); + flags &= ~kWriteSourceSuspended; + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Reading +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)readDataWithTimeout:(NSTimeInterval)timeout tag:(long)tag +{ + [self readDataWithTimeout:timeout buffer:nil bufferOffset:0 maxLength:0 tag:tag]; +} + +- (void)readDataWithTimeout:(NSTimeInterval)timeout + buffer:(NSMutableData *)buffer + bufferOffset:(NSUInteger)offset + tag:(long)tag +{ + [self readDataWithTimeout:timeout buffer:buffer bufferOffset:offset maxLength:0 tag:tag]; +} + +- (void)readDataWithTimeout:(NSTimeInterval)timeout + buffer:(NSMutableData *)buffer + bufferOffset:(NSUInteger)offset + maxLength:(NSUInteger)length + tag:(long)tag +{ + if (offset > [buffer length]) { + LogWarn(@"Cannot read: offset > [buffer length]"); + return; + } + + GCDAsyncReadPacket *packet = [[GCDAsyncReadPacket alloc] initWithData:buffer + startOffset:offset + maxLength:length + timeout:timeout + readLength:0 + terminator:nil + tag:tag]; + + dispatch_async(socketQueue, ^{ @autoreleasepool { + + LogTrace(); + + if ((flags & kSocketStarted) && !(flags & kForbidReadsWrites)) + { + [readQueue addObject:packet]; + [self maybeDequeueRead]; + } + }}); + + // Do not rely on the block being run in order to release the packet, + // as the queue might get released without the block completing. +} + +- (void)readDataToLength:(NSUInteger)length withTimeout:(NSTimeInterval)timeout tag:(long)tag +{ + [self readDataToLength:length withTimeout:timeout buffer:nil bufferOffset:0 tag:tag]; +} + +- (void)readDataToLength:(NSUInteger)length + withTimeout:(NSTimeInterval)timeout + buffer:(NSMutableData *)buffer + bufferOffset:(NSUInteger)offset + tag:(long)tag +{ + if (length == 0) { + LogWarn(@"Cannot read: length == 0"); + return; + } + if (offset > [buffer length]) { + LogWarn(@"Cannot read: offset > [buffer length]"); + return; + } + + GCDAsyncReadPacket *packet = [[GCDAsyncReadPacket alloc] initWithData:buffer + startOffset:offset + maxLength:0 + timeout:timeout + readLength:length + terminator:nil + tag:tag]; + + dispatch_async(socketQueue, ^{ @autoreleasepool { + + LogTrace(); + + if ((flags & kSocketStarted) && !(flags & kForbidReadsWrites)) + { + [readQueue addObject:packet]; + [self maybeDequeueRead]; + } + }}); + + // Do not rely on the block being run in order to release the packet, + // as the queue might get released without the block completing. +} + +- (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag +{ + [self readDataToData:data withTimeout:timeout buffer:nil bufferOffset:0 maxLength:0 tag:tag]; +} + +- (void)readDataToData:(NSData *)data + withTimeout:(NSTimeInterval)timeout + buffer:(NSMutableData *)buffer + bufferOffset:(NSUInteger)offset + tag:(long)tag +{ + [self readDataToData:data withTimeout:timeout buffer:buffer bufferOffset:offset maxLength:0 tag:tag]; +} + +- (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout maxLength:(NSUInteger)length tag:(long)tag +{ + [self readDataToData:data withTimeout:timeout buffer:nil bufferOffset:0 maxLength:length tag:tag]; +} + +- (void)readDataToData:(NSData *)data + withTimeout:(NSTimeInterval)timeout + buffer:(NSMutableData *)buffer + bufferOffset:(NSUInteger)offset + maxLength:(NSUInteger)maxLength + tag:(long)tag +{ + if ([data length] == 0) { + LogWarn(@"Cannot read: [data length] == 0"); + return; + } + if (offset > [buffer length]) { + LogWarn(@"Cannot read: offset > [buffer length]"); + return; + } + if (maxLength > 0 && maxLength < [data length]) { + LogWarn(@"Cannot read: maxLength > 0 && maxLength < [data length]"); + return; + } + + GCDAsyncReadPacket *packet = [[GCDAsyncReadPacket alloc] initWithData:buffer + startOffset:offset + maxLength:maxLength + timeout:timeout + readLength:0 + terminator:data + tag:tag]; + + dispatch_async(socketQueue, ^{ @autoreleasepool { + + LogTrace(); + + if ((flags & kSocketStarted) && !(flags & kForbidReadsWrites)) + { + [readQueue addObject:packet]; + [self maybeDequeueRead]; + } + }}); + + // Do not rely on the block being run in order to release the packet, + // as the queue might get released without the block completing. +} + +- (float)progressOfReadReturningTag:(long *)tagPtr bytesDone:(NSUInteger *)donePtr total:(NSUInteger *)totalPtr +{ + __block float result = 0.0F; + + dispatch_block_t block = ^{ + + if (!currentRead || ![currentRead isKindOfClass:[GCDAsyncReadPacket class]]) + { + // We're not reading anything right now. + + if (tagPtr != NULL) *tagPtr = 0; + if (donePtr != NULL) *donePtr = 0; + if (totalPtr != NULL) *totalPtr = 0; + + result = NAN; + } + else + { + // It's only possible to know the progress of our read if we're reading to a certain length. + // If we're reading to data, we of course have no idea when the data will arrive. + // If we're reading to timeout, then we have no idea when the next chunk of data will arrive. + + NSUInteger done = currentRead->bytesDone; + NSUInteger total = currentRead->readLength; + + if (tagPtr != NULL) *tagPtr = currentRead->tag; + if (donePtr != NULL) *donePtr = done; + if (totalPtr != NULL) *totalPtr = total; + + if (total > 0) + result = (float)done / (float)total; + else + result = 1.0F; + } + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + return result; +} + +/** + * This method starts a new read, if needed. + * + * It is called when: + * - a user requests a read + * - after a read request has finished (to handle the next request) + * - immediately after the socket opens to handle any pending requests + * + * This method also handles auto-disconnect post read/write completion. +**/ +- (void)maybeDequeueRead +{ + LogTrace(); + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + + // If we're not currently processing a read AND we have an available read stream + if ((currentRead == nil) && (flags & kConnected)) + { + if ([readQueue count] > 0) + { + // Dequeue the next object in the write queue + currentRead = [readQueue objectAtIndex:0]; + [readQueue removeObjectAtIndex:0]; + + + if ([currentRead isKindOfClass:[GCDAsyncSpecialPacket class]]) + { + LogVerbose(@"Dequeued GCDAsyncSpecialPacket"); + + // Attempt to start TLS + flags |= kStartingReadTLS; + + // This method won't do anything unless both kStartingReadTLS and kStartingWriteTLS are set + [self maybeStartTLS]; + } + else + { + LogVerbose(@"Dequeued GCDAsyncReadPacket"); + + // Setup read timer (if needed) + [self setupReadTimerWithTimeout:currentRead->timeout]; + + // Immediately read, if possible + [self doReadData]; + } + } + else if (flags & kDisconnectAfterReads) + { + if (flags & kDisconnectAfterWrites) + { + if (([writeQueue count] == 0) && (currentWrite == nil)) + { + [self closeWithError:nil]; + } + } + else + { + [self closeWithError:nil]; + } + } + else if (flags & kSocketSecure) + { + [self flushSSLBuffers]; + + // Edge case: + // + // We just drained all data from the ssl buffers, + // and all known data from the socket (socketFDBytesAvailable). + // + // If we didn't get any data from this process, + // then we may have reached the end of the TCP stream. + // + // Be sure callbacks are enabled so we're notified about a disconnection. + + if ([preBuffer availableBytes] == 0) + { + if ([self usingCFStreamForTLS]) { + // Callbacks never disabled + } + else { + [self resumeReadSource]; + } + } + } + } +} + +- (void)flushSSLBuffers +{ + LogTrace(); + + NSAssert((flags & kSocketSecure), @"Cannot flush ssl buffers on non-secure socket"); + + if ([preBuffer availableBytes] > 0) + { + // Only flush the ssl buffers if the prebuffer is empty. + // This is to avoid growing the prebuffer inifinitely large. + + return; + } + + #if TARGET_OS_IPHONE + + if ([self usingCFStreamForTLS]) + { + if ((flags & kSecureSocketHasBytesAvailable) && CFReadStreamHasBytesAvailable(readStream)) + { + LogVerbose(@"%@ - Flushing ssl buffers into prebuffer...", THIS_METHOD); + + CFIndex defaultBytesToRead = (1024 * 4); + + [preBuffer ensureCapacityForWrite:defaultBytesToRead]; + + uint8_t *buffer = [preBuffer writeBuffer]; + + CFIndex result = CFReadStreamRead(readStream, buffer, defaultBytesToRead); + LogVerbose(@"%@ - CFReadStreamRead(): result = %i", THIS_METHOD, (int)result); + + if (result > 0) + { + [preBuffer didWrite:result]; + } + + flags &= ~kSecureSocketHasBytesAvailable; + } + + return; + } + + #endif + + __block NSUInteger estimatedBytesAvailable = 0; + + dispatch_block_t updateEstimatedBytesAvailable = ^{ + + // Figure out if there is any data available to be read + // + // socketFDBytesAvailable <- Number of encrypted bytes we haven't read from the bsd socket + // [sslPreBuffer availableBytes] <- Number of encrypted bytes we've buffered from bsd socket + // sslInternalBufSize <- Number of decrypted bytes SecureTransport has buffered + // + // We call the variable "estimated" because we don't know how many decrypted bytes we'll get + // from the encrypted bytes in the sslPreBuffer. + // However, we do know this is an upper bound on the estimation. + + estimatedBytesAvailable = socketFDBytesAvailable + [sslPreBuffer availableBytes]; + + size_t sslInternalBufSize = 0; + SSLGetBufferedReadSize(sslContext, &sslInternalBufSize); + + estimatedBytesAvailable += sslInternalBufSize; + }; + + updateEstimatedBytesAvailable(); + + if (estimatedBytesAvailable > 0) + { + LogVerbose(@"%@ - Flushing ssl buffers into prebuffer...", THIS_METHOD); + + BOOL done = NO; + do + { + LogVerbose(@"%@ - estimatedBytesAvailable = %lu", THIS_METHOD, (unsigned long)estimatedBytesAvailable); + + // Make sure there's enough room in the prebuffer + + [preBuffer ensureCapacityForWrite:estimatedBytesAvailable]; + + // Read data into prebuffer + + uint8_t *buffer = [preBuffer writeBuffer]; + size_t bytesRead = 0; + + OSStatus result = SSLRead(sslContext, buffer, (size_t)estimatedBytesAvailable, &bytesRead); + LogVerbose(@"%@ - read from secure socket = %u", THIS_METHOD, (unsigned)bytesRead); + + if (bytesRead > 0) + { + [preBuffer didWrite:bytesRead]; + } + + LogVerbose(@"%@ - prebuffer.length = %zu", THIS_METHOD, [preBuffer availableBytes]); + + if (result != noErr) + { + done = YES; + } + else + { + updateEstimatedBytesAvailable(); + } + + } while (!done && estimatedBytesAvailable > 0); + } +} + +- (void)doReadData +{ + LogTrace(); + + // This method is called on the socketQueue. + // It might be called directly, or via the readSource when data is available to be read. + + if ((currentRead == nil) || (flags & kReadsPaused)) + { + LogVerbose(@"No currentRead or kReadsPaused"); + + // Unable to read at this time + + if (flags & kSocketSecure) + { + // Here's the situation: + // + // We have an established secure connection. + // There may not be a currentRead, but there might be encrypted data sitting around for us. + // When the user does get around to issuing a read, that encrypted data will need to be decrypted. + // + // So why make the user wait? + // We might as well get a head start on decrypting some data now. + // + // The other reason we do this has to do with detecting a socket disconnection. + // The SSL/TLS protocol has it's own disconnection handshake. + // So when a secure socket is closed, a "goodbye" packet comes across the wire. + // We want to make sure we read the "goodbye" packet so we can properly detect the TCP disconnection. + + [self flushSSLBuffers]; + } + + if ([self usingCFStreamForTLS]) + { + // CFReadStream only fires once when there is available data. + // It won't fire again until we've invoked CFReadStreamRead. + } + else + { + // If the readSource is firing, we need to pause it + // or else it will continue to fire over and over again. + // + // If the readSource is not firing, + // we want it to continue monitoring the socket. + + if (socketFDBytesAvailable > 0) + { + [self suspendReadSource]; + } + } + return; + } + + BOOL hasBytesAvailable = NO; + unsigned long estimatedBytesAvailable = 0; + + if ([self usingCFStreamForTLS]) + { + #if TARGET_OS_IPHONE + + // Requested CFStream, rather than SecureTransport, for TLS (via GCDAsyncSocketUseCFStreamForTLS) + + estimatedBytesAvailable = 0; + if ((flags & kSecureSocketHasBytesAvailable) && CFReadStreamHasBytesAvailable(readStream)) + hasBytesAvailable = YES; + else + hasBytesAvailable = NO; + + #endif + } + else + { + estimatedBytesAvailable = socketFDBytesAvailable; + + if (flags & kSocketSecure) + { + // There are 2 buffers to be aware of here. + // + // We are using SecureTransport, a TLS/SSL security layer which sits atop TCP. + // We issue a read to the SecureTranport API, which in turn issues a read to our SSLReadFunction. + // Our SSLReadFunction then reads from the BSD socket and returns the encrypted data to SecureTransport. + // SecureTransport then decrypts the data, and finally returns the decrypted data back to us. + // + // The first buffer is one we create. + // SecureTransport often requests small amounts of data. + // This has to do with the encypted packets that are coming across the TCP stream. + // But it's non-optimal to do a bunch of small reads from the BSD socket. + // So our SSLReadFunction reads all available data from the socket (optimizing the sys call) + // and may store excess in the sslPreBuffer. + + estimatedBytesAvailable += [sslPreBuffer availableBytes]; + + // The second buffer is within SecureTransport. + // As mentioned earlier, there are encrypted packets coming across the TCP stream. + // SecureTransport needs the entire packet to decrypt it. + // But if the entire packet produces X bytes of decrypted data, + // and we only asked SecureTransport for X/2 bytes of data, + // it must store the extra X/2 bytes of decrypted data for the next read. + // + // The SSLGetBufferedReadSize function will tell us the size of this internal buffer. + // From the documentation: + // + // "This function does not block or cause any low-level read operations to occur." + + size_t sslInternalBufSize = 0; + SSLGetBufferedReadSize(sslContext, &sslInternalBufSize); + + estimatedBytesAvailable += sslInternalBufSize; + } + + hasBytesAvailable = (estimatedBytesAvailable > 0); + } + + if ((hasBytesAvailable == NO) && ([preBuffer availableBytes] == 0)) + { + LogVerbose(@"No data available to read..."); + + // No data available to read. + + if (![self usingCFStreamForTLS]) + { + // Need to wait for readSource to fire and notify us of + // available data in the socket's internal read buffer. + + [self resumeReadSource]; + } + return; + } + + if (flags & kStartingReadTLS) + { + LogVerbose(@"Waiting for SSL/TLS handshake to complete"); + + // The readQueue is waiting for SSL/TLS handshake to complete. + + if (flags & kStartingWriteTLS) + { + if ([self usingSecureTransportForTLS] && lastSSLHandshakeError == errSSLWouldBlock) + { + // We are in the process of a SSL Handshake. + // We were waiting for incoming data which has just arrived. + + [self ssl_continueSSLHandshake]; + } + } + else + { + // We are still waiting for the writeQueue to drain and start the SSL/TLS process. + // We now know data is available to read. + + if (![self usingCFStreamForTLS]) + { + // Suspend the read source or else it will continue to fire nonstop. + + [self suspendReadSource]; + } + } + + return; + } + + BOOL done = NO; // Completed read operation + NSError *error = nil; // Error occurred + + NSUInteger totalBytesReadForCurrentRead = 0; + + // + // STEP 1 - READ FROM PREBUFFER + // + + if ([preBuffer availableBytes] > 0) + { + // There are 3 types of read packets: + // + // 1) Read all available data. + // 2) Read a specific length of data. + // 3) Read up to a particular terminator. + + NSUInteger bytesToCopy; + + if (currentRead->term != nil) + { + // Read type #3 - read up to a terminator + + bytesToCopy = [currentRead readLengthForTermWithPreBuffer:preBuffer found:&done]; + } + else + { + // Read type #1 or #2 + + bytesToCopy = [currentRead readLengthForNonTermWithHint:[preBuffer availableBytes]]; + } + + // Make sure we have enough room in the buffer for our read. + + [currentRead ensureCapacityForAdditionalDataOfLength:bytesToCopy]; + + // Copy bytes from prebuffer into packet buffer + + uint8_t *buffer = (uint8_t *)[currentRead->buffer mutableBytes] + currentRead->startOffset + + currentRead->bytesDone; + + memcpy(buffer, [preBuffer readBuffer], bytesToCopy); + + // Remove the copied bytes from the preBuffer + [preBuffer didRead:bytesToCopy]; + + LogVerbose(@"copied(%lu) preBufferLength(%zu)", (unsigned long)bytesToCopy, [preBuffer availableBytes]); + + // Update totals + + currentRead->bytesDone += bytesToCopy; + totalBytesReadForCurrentRead += bytesToCopy; + + // Check to see if the read operation is done + + if (currentRead->readLength > 0) + { + // Read type #2 - read a specific length of data + + done = (currentRead->bytesDone == currentRead->readLength); + } + else if (currentRead->term != nil) + { + // Read type #3 - read up to a terminator + + // Our 'done' variable was updated via the readLengthForTermWithPreBuffer:found: method + + if (!done && currentRead->maxLength > 0) + { + // We're not done and there's a set maxLength. + // Have we reached that maxLength yet? + + if (currentRead->bytesDone >= currentRead->maxLength) + { + error = [self readMaxedOutError]; + } + } + } + else + { + // Read type #1 - read all available data + // + // We're done as soon as + // - we've read all available data (in prebuffer and socket) + // - we've read the maxLength of read packet. + + done = ((currentRead->maxLength > 0) && (currentRead->bytesDone == currentRead->maxLength)); + } + + } + + // + // STEP 2 - READ FROM SOCKET + // + + BOOL socketEOF = (flags & kSocketHasReadEOF) ? YES : NO; // Nothing more to read via socket (end of file) + BOOL waiting = !done && !error && !socketEOF && !hasBytesAvailable; // Ran out of data, waiting for more + + if (!done && !error && !socketEOF && hasBytesAvailable) + { + NSAssert(([preBuffer availableBytes] == 0), @"Invalid logic"); + + BOOL readIntoPreBuffer = NO; + uint8_t *buffer = NULL; + size_t bytesRead = 0; + + if (flags & kSocketSecure) + { + if ([self usingCFStreamForTLS]) + { + #if TARGET_OS_IPHONE + + // Using CFStream, rather than SecureTransport, for TLS + + NSUInteger defaultReadLength = (1024 * 32); + + NSUInteger bytesToRead = [currentRead optimalReadLengthWithDefault:defaultReadLength + shouldPreBuffer:&readIntoPreBuffer]; + + // Make sure we have enough room in the buffer for our read. + // + // We are either reading directly into the currentRead->buffer, + // or we're reading into the temporary preBuffer. + + if (readIntoPreBuffer) + { + [preBuffer ensureCapacityForWrite:bytesToRead]; + + buffer = [preBuffer writeBuffer]; + } + else + { + [currentRead ensureCapacityForAdditionalDataOfLength:bytesToRead]; + + buffer = (uint8_t *)[currentRead->buffer mutableBytes] + + currentRead->startOffset + + currentRead->bytesDone; + } + + // Read data into buffer + + CFIndex result = CFReadStreamRead(readStream, buffer, (CFIndex)bytesToRead); + LogVerbose(@"CFReadStreamRead(): result = %i", (int)result); + + if (result < 0) + { + error = (__bridge_transfer NSError *)CFReadStreamCopyError(readStream); + } + else if (result == 0) + { + socketEOF = YES; + } + else + { + waiting = YES; + bytesRead = (size_t)result; + } + + // We only know how many decrypted bytes were read. + // The actual number of bytes read was likely more due to the overhead of the encryption. + // So we reset our flag, and rely on the next callback to alert us of more data. + flags &= ~kSecureSocketHasBytesAvailable; + + #endif + } + else + { + // Using SecureTransport for TLS + // + // We know: + // - how many bytes are available on the socket + // - how many encrypted bytes are sitting in the sslPreBuffer + // - how many decypted bytes are sitting in the sslContext + // + // But we do NOT know: + // - how many encypted bytes are sitting in the sslContext + // + // So we play the regular game of using an upper bound instead. + + NSUInteger defaultReadLength = (1024 * 32); + + if (defaultReadLength < estimatedBytesAvailable) { + defaultReadLength = estimatedBytesAvailable + (1024 * 16); + } + + NSUInteger bytesToRead = [currentRead optimalReadLengthWithDefault:defaultReadLength + shouldPreBuffer:&readIntoPreBuffer]; + + if (bytesToRead > SIZE_MAX) { // NSUInteger may be bigger than size_t + bytesToRead = SIZE_MAX; + } + + // Make sure we have enough room in the buffer for our read. + // + // We are either reading directly into the currentRead->buffer, + // or we're reading into the temporary preBuffer. + + if (readIntoPreBuffer) + { + [preBuffer ensureCapacityForWrite:bytesToRead]; + + buffer = [preBuffer writeBuffer]; + } + else + { + [currentRead ensureCapacityForAdditionalDataOfLength:bytesToRead]; + + buffer = (uint8_t *)[currentRead->buffer mutableBytes] + + currentRead->startOffset + + currentRead->bytesDone; + } + + // The documentation from Apple states: + // + // "a read operation might return errSSLWouldBlock, + // indicating that less data than requested was actually transferred" + // + // However, starting around 10.7, the function will sometimes return noErr, + // even if it didn't read as much data as requested. So we need to watch out for that. + + OSStatus result; + do + { + void *loop_buffer = buffer + bytesRead; + size_t loop_bytesToRead = (size_t)bytesToRead - bytesRead; + size_t loop_bytesRead = 0; + + result = SSLRead(sslContext, loop_buffer, loop_bytesToRead, &loop_bytesRead); + LogVerbose(@"read from secure socket = %u", (unsigned)loop_bytesRead); + + bytesRead += loop_bytesRead; + + } while ((result == noErr) && (bytesRead < bytesToRead)); + + + if (result != noErr) + { + if (result == errSSLWouldBlock) + waiting = YES; + else + { + if (result == errSSLClosedGraceful || result == errSSLClosedAbort) + { + // We've reached the end of the stream. + // Handle this the same way we would an EOF from the socket. + socketEOF = YES; + sslErrCode = result; + } + else + { + error = [self sslError:result]; + } + } + // It's possible that bytesRead > 0, even if the result was errSSLWouldBlock. + // This happens when the SSLRead function is able to read some data, + // but not the entire amount we requested. + + if (bytesRead <= 0) + { + bytesRead = 0; + } + } + + // Do not modify socketFDBytesAvailable. + // It will be updated via the SSLReadFunction(). + } + } + else + { + // Normal socket operation + + NSUInteger bytesToRead; + + // There are 3 types of read packets: + // + // 1) Read all available data. + // 2) Read a specific length of data. + // 3) Read up to a particular terminator. + + if (currentRead->term != nil) + { + // Read type #3 - read up to a terminator + + bytesToRead = [currentRead readLengthForTermWithHint:estimatedBytesAvailable + shouldPreBuffer:&readIntoPreBuffer]; + } + else + { + // Read type #1 or #2 + + bytesToRead = [currentRead readLengthForNonTermWithHint:estimatedBytesAvailable]; + } + + if (bytesToRead > SIZE_MAX) { // NSUInteger may be bigger than size_t (read param 3) + bytesToRead = SIZE_MAX; + } + + // Make sure we have enough room in the buffer for our read. + // + // We are either reading directly into the currentRead->buffer, + // or we're reading into the temporary preBuffer. + + if (readIntoPreBuffer) + { + [preBuffer ensureCapacityForWrite:bytesToRead]; + + buffer = [preBuffer writeBuffer]; + } + else + { + [currentRead ensureCapacityForAdditionalDataOfLength:bytesToRead]; + + buffer = (uint8_t *)[currentRead->buffer mutableBytes] + + currentRead->startOffset + + currentRead->bytesDone; + } + + // Read data into buffer + + int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN; + + ssize_t result = read(socketFD, buffer, (size_t)bytesToRead); + LogVerbose(@"read from socket = %i", (int)result); + + if (result < 0) + { + if (errno == EWOULDBLOCK) + waiting = YES; + else + error = [self errnoErrorWithReason:@"Error in read() function"]; + + socketFDBytesAvailable = 0; + } + else if (result == 0) + { + socketEOF = YES; + socketFDBytesAvailable = 0; + } + else + { + bytesRead = result; + + if (bytesRead < bytesToRead) + { + // The read returned less data than requested. + // This means socketFDBytesAvailable was a bit off due to timing, + // because we read from the socket right when the readSource event was firing. + socketFDBytesAvailable = 0; + } + else + { + if (socketFDBytesAvailable <= bytesRead) + socketFDBytesAvailable = 0; + else + socketFDBytesAvailable -= bytesRead; + } + + if (socketFDBytesAvailable == 0) + { + waiting = YES; + } + } + } + + if (bytesRead > 0) + { + // Check to see if the read operation is done + + if (currentRead->readLength > 0) + { + // Read type #2 - read a specific length of data + // + // Note: We should never be using a prebuffer when we're reading a specific length of data. + + NSAssert(readIntoPreBuffer == NO, @"Invalid logic"); + + currentRead->bytesDone += bytesRead; + totalBytesReadForCurrentRead += bytesRead; + + done = (currentRead->bytesDone == currentRead->readLength); + } + else if (currentRead->term != nil) + { + // Read type #3 - read up to a terminator + + if (readIntoPreBuffer) + { + // We just read a big chunk of data into the preBuffer + + [preBuffer didWrite:bytesRead]; + LogVerbose(@"read data into preBuffer - preBuffer.length = %zu", [preBuffer availableBytes]); + + // Search for the terminating sequence + + NSUInteger bytesToCopy = [currentRead readLengthForTermWithPreBuffer:preBuffer found:&done]; + LogVerbose(@"copying %lu bytes from preBuffer", (unsigned long)bytesToCopy); + + // Ensure there's room on the read packet's buffer + + [currentRead ensureCapacityForAdditionalDataOfLength:bytesToCopy]; + + // Copy bytes from prebuffer into read buffer + + uint8_t *readBuf = (uint8_t *)[currentRead->buffer mutableBytes] + currentRead->startOffset + + currentRead->bytesDone; + + memcpy(readBuf, [preBuffer readBuffer], bytesToCopy); + + // Remove the copied bytes from the prebuffer + [preBuffer didRead:bytesToCopy]; + LogVerbose(@"preBuffer.length = %zu", [preBuffer availableBytes]); + + // Update totals + currentRead->bytesDone += bytesToCopy; + totalBytesReadForCurrentRead += bytesToCopy; + + // Our 'done' variable was updated via the readLengthForTermWithPreBuffer:found: method above + } + else + { + // We just read a big chunk of data directly into the packet's buffer. + // We need to move any overflow into the prebuffer. + + NSInteger overflow = [currentRead searchForTermAfterPreBuffering:bytesRead]; + + if (overflow == 0) + { + // Perfect match! + // Every byte we read stays in the read buffer, + // and the last byte we read was the last byte of the term. + + currentRead->bytesDone += bytesRead; + totalBytesReadForCurrentRead += bytesRead; + done = YES; + } + else if (overflow > 0) + { + // The term was found within the data that we read, + // and there are extra bytes that extend past the end of the term. + // We need to move these excess bytes out of the read packet and into the prebuffer. + + NSInteger underflow = bytesRead - overflow; + + // Copy excess data into preBuffer + + LogVerbose(@"copying %ld overflow bytes into preBuffer", (long)overflow); + [preBuffer ensureCapacityForWrite:overflow]; + + uint8_t *overflowBuffer = buffer + underflow; + memcpy([preBuffer writeBuffer], overflowBuffer, overflow); + + [preBuffer didWrite:overflow]; + LogVerbose(@"preBuffer.length = %zu", [preBuffer availableBytes]); + + // Note: The completeCurrentRead method will trim the buffer for us. + + currentRead->bytesDone += underflow; + totalBytesReadForCurrentRead += underflow; + done = YES; + } + else + { + // The term was not found within the data that we read. + + currentRead->bytesDone += bytesRead; + totalBytesReadForCurrentRead += bytesRead; + done = NO; + } + } + + if (!done && currentRead->maxLength > 0) + { + // We're not done and there's a set maxLength. + // Have we reached that maxLength yet? + + if (currentRead->bytesDone >= currentRead->maxLength) + { + error = [self readMaxedOutError]; + } + } + } + else + { + // Read type #1 - read all available data + + if (readIntoPreBuffer) + { + // We just read a chunk of data into the preBuffer + + [preBuffer didWrite:bytesRead]; + + // Now copy the data into the read packet. + // + // Recall that we didn't read directly into the packet's buffer to avoid + // over-allocating memory since we had no clue how much data was available to be read. + // + // Ensure there's room on the read packet's buffer + + [currentRead ensureCapacityForAdditionalDataOfLength:bytesRead]; + + // Copy bytes from prebuffer into read buffer + + uint8_t *readBuf = (uint8_t *)[currentRead->buffer mutableBytes] + currentRead->startOffset + + currentRead->bytesDone; + + memcpy(readBuf, [preBuffer readBuffer], bytesRead); + + // Remove the copied bytes from the prebuffer + [preBuffer didRead:bytesRead]; + + // Update totals + currentRead->bytesDone += bytesRead; + totalBytesReadForCurrentRead += bytesRead; + } + else + { + currentRead->bytesDone += bytesRead; + totalBytesReadForCurrentRead += bytesRead; + } + + done = YES; + } + + } // if (bytesRead > 0) + + } // if (!done && !error && !socketEOF && hasBytesAvailable) + + + if (!done && currentRead->readLength == 0 && currentRead->term == nil) + { + // Read type #1 - read all available data + // + // We might arrive here if we read data from the prebuffer but not from the socket. + + done = (totalBytesReadForCurrentRead > 0); + } + + // Check to see if we're done, or if we've made progress + + if (done) + { + [self completeCurrentRead]; + + if (!error && (!socketEOF || [preBuffer availableBytes] > 0)) + { + [self maybeDequeueRead]; + } + } + else if (totalBytesReadForCurrentRead > 0) + { + // We're not done read type #2 or #3 yet, but we have read in some bytes + + __strong id theDelegate = delegate; + + if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:didReadPartialDataOfLength:tag:)]) + { + long theReadTag = currentRead->tag; + + dispatch_async(delegateQueue, ^{ @autoreleasepool { + + [theDelegate socket:self didReadPartialDataOfLength:totalBytesReadForCurrentRead tag:theReadTag]; + }}); + } + } + + // Check for errors + + if (error) + { + [self closeWithError:error]; + } + else if (socketEOF) + { + [self doReadEOF]; + } + else if (waiting) + { + if (![self usingCFStreamForTLS]) + { + // Monitor the socket for readability (if we're not already doing so) + [self resumeReadSource]; + } + } + + // Do not add any code here without first adding return statements in the error cases above. +} + +- (void)doReadEOF +{ + LogTrace(); + + // This method may be called more than once. + // If the EOF is read while there is still data in the preBuffer, + // then this method may be called continually after invocations of doReadData to see if it's time to disconnect. + + flags |= kSocketHasReadEOF; + + if (flags & kSocketSecure) + { + // If the SSL layer has any buffered data, flush it into the preBuffer now. + + [self flushSSLBuffers]; + } + + BOOL shouldDisconnect = NO; + NSError *error = nil; + + if ((flags & kStartingReadTLS) || (flags & kStartingWriteTLS)) + { + // We received an EOF during or prior to startTLS. + // The SSL/TLS handshake is now impossible, so this is an unrecoverable situation. + + shouldDisconnect = YES; + + if ([self usingSecureTransportForTLS]) + { + error = [self sslError:errSSLClosedAbort]; + } + } + else if (flags & kReadStreamClosed) + { + // The preBuffer has already been drained. + // The config allows half-duplex connections. + // We've previously checked the socket, and it appeared writeable. + // So we marked the read stream as closed and notified the delegate. + // + // As per the half-duplex contract, the socket will be closed when a write fails, + // or when the socket is manually closed. + + shouldDisconnect = NO; + } + else if ([preBuffer availableBytes] > 0) + { + LogVerbose(@"Socket reached EOF, but there is still data available in prebuffer"); + + // Although we won't be able to read any more data from the socket, + // there is existing data that has been prebuffered that we can read. + + shouldDisconnect = NO; + } + else if (config & kAllowHalfDuplexConnection) + { + // We just received an EOF (end of file) from the socket's read stream. + // This means the remote end of the socket (the peer we're connected to) + // has explicitly stated that it will not be sending us any more data. + // + // Query the socket to see if it is still writeable. (Perhaps the peer will continue reading data from us) + + int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN; + + struct pollfd pfd[1]; + pfd[0].fd = socketFD; + pfd[0].events = POLLOUT; + pfd[0].revents = 0; + + poll(pfd, 1, 0); + + if (pfd[0].revents & POLLOUT) + { + // Socket appears to still be writeable + + shouldDisconnect = NO; + flags |= kReadStreamClosed; + + // Notify the delegate that we're going half-duplex + + __strong id theDelegate = delegate; + + if (delegateQueue && [theDelegate respondsToSelector:@selector(socketDidCloseReadStream:)]) + { + dispatch_async(delegateQueue, ^{ @autoreleasepool { + + [theDelegate socketDidCloseReadStream:self]; + }}); + } + } + else + { + shouldDisconnect = YES; + } + } + else + { + shouldDisconnect = YES; + } + + + if (shouldDisconnect) + { + if (error == nil) + { + if ([self usingSecureTransportForTLS]) + { + if (sslErrCode != noErr && sslErrCode != errSSLClosedGraceful) + { + error = [self sslError:sslErrCode]; + } + else + { + error = [self connectionClosedError]; + } + } + else + { + error = [self connectionClosedError]; + } + } + [self closeWithError:error]; + } + else + { + if (![self usingCFStreamForTLS]) + { + // Suspend the read source (if needed) + + [self suspendReadSource]; + } + } +} + +- (void)completeCurrentRead +{ + LogTrace(); + + NSAssert(currentRead, @"Trying to complete current read when there is no current read."); + + + NSData *result = nil; + + if (currentRead->bufferOwner) + { + // We created the buffer on behalf of the user. + // Trim our buffer to be the proper size. + [currentRead->buffer setLength:currentRead->bytesDone]; + + result = currentRead->buffer; + } + else + { + // We did NOT create the buffer. + // The buffer is owned by the caller. + // Only trim the buffer if we had to increase its size. + + if ([currentRead->buffer length] > currentRead->originalBufferLength) + { + NSUInteger readSize = currentRead->startOffset + currentRead->bytesDone; + NSUInteger origSize = currentRead->originalBufferLength; + + NSUInteger buffSize = MAX(readSize, origSize); + + [currentRead->buffer setLength:buffSize]; + } + + uint8_t *buffer = (uint8_t *)[currentRead->buffer mutableBytes] + currentRead->startOffset; + + result = [NSData dataWithBytesNoCopy:buffer length:currentRead->bytesDone freeWhenDone:NO]; + } + + __strong id theDelegate = delegate; + + if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:didReadData:withTag:)]) + { + GCDAsyncReadPacket *theRead = currentRead; // Ensure currentRead retained since result may not own buffer + + dispatch_async(delegateQueue, ^{ @autoreleasepool { + + [theDelegate socket:self didReadData:result withTag:theRead->tag]; + }}); + } + + [self endCurrentRead]; +} + +- (void)endCurrentRead +{ + if (readTimer) + { + dispatch_source_cancel(readTimer); + readTimer = NULL; + } + + currentRead = nil; +} + +- (void)setupReadTimerWithTimeout:(NSTimeInterval)timeout +{ + if (timeout >= 0.0) + { + readTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, socketQueue); + + __weak GCDAsyncSocket *weakSelf = self; + + dispatch_source_set_event_handler(readTimer, ^{ @autoreleasepool { + #pragma clang diagnostic push + #pragma clang diagnostic warning "-Wimplicit-retain-self" + + __strong GCDAsyncSocket *strongSelf = weakSelf; + if (strongSelf == nil) return_from_block; + + [strongSelf doReadTimeout]; + + #pragma clang diagnostic pop + }}); + + #if !OS_OBJECT_USE_OBJC + dispatch_source_t theReadTimer = readTimer; + dispatch_source_set_cancel_handler(readTimer, ^{ + #pragma clang diagnostic push + #pragma clang diagnostic warning "-Wimplicit-retain-self" + + LogVerbose(@"dispatch_release(readTimer)"); + dispatch_release(theReadTimer); + + #pragma clang diagnostic pop + }); + #endif + + dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC)); + + dispatch_source_set_timer(readTimer, tt, DISPATCH_TIME_FOREVER, 0); + dispatch_resume(readTimer); + } +} + +- (void)doReadTimeout +{ + // This is a little bit tricky. + // Ideally we'd like to synchronously query the delegate about a timeout extension. + // But if we do so synchronously we risk a possible deadlock. + // So instead we have to do so asynchronously, and callback to ourselves from within the delegate block. + + flags |= kReadsPaused; + + __strong id theDelegate = delegate; + + if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:shouldTimeoutReadWithTag:elapsed:bytesDone:)]) + { + GCDAsyncReadPacket *theRead = currentRead; + + dispatch_async(delegateQueue, ^{ @autoreleasepool { + + NSTimeInterval timeoutExtension = 0.0; + + timeoutExtension = [theDelegate socket:self shouldTimeoutReadWithTag:theRead->tag + elapsed:theRead->timeout + bytesDone:theRead->bytesDone]; + + dispatch_async(socketQueue, ^{ @autoreleasepool { + + [self doReadTimeoutWithExtension:timeoutExtension]; + }}); + }}); + } + else + { + [self doReadTimeoutWithExtension:0.0]; + } +} + +- (void)doReadTimeoutWithExtension:(NSTimeInterval)timeoutExtension +{ + if (currentRead) + { + if (timeoutExtension > 0.0) + { + currentRead->timeout += timeoutExtension; + + // Reschedule the timer + dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeoutExtension * NSEC_PER_SEC)); + dispatch_source_set_timer(readTimer, tt, DISPATCH_TIME_FOREVER, 0); + + // Unpause reads, and continue + flags &= ~kReadsPaused; + [self doReadData]; + } + else + { + LogVerbose(@"ReadTimeout"); + + [self closeWithError:[self readTimeoutError]]; + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Writing +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)writeData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag +{ + if ([data length] == 0) return; + + GCDAsyncWritePacket *packet = [[GCDAsyncWritePacket alloc] initWithData:data timeout:timeout tag:tag]; + + dispatch_async(socketQueue, ^{ @autoreleasepool { + + LogTrace(); + + if ((flags & kSocketStarted) && !(flags & kForbidReadsWrites)) + { + [writeQueue addObject:packet]; + [self maybeDequeueWrite]; + } + }}); + + // Do not rely on the block being run in order to release the packet, + // as the queue might get released without the block completing. +} + +- (float)progressOfWriteReturningTag:(long *)tagPtr bytesDone:(NSUInteger *)donePtr total:(NSUInteger *)totalPtr +{ + __block float result = 0.0F; + + dispatch_block_t block = ^{ + + if (!currentWrite || ![currentWrite isKindOfClass:[GCDAsyncWritePacket class]]) + { + // We're not writing anything right now. + + if (tagPtr != NULL) *tagPtr = 0; + if (donePtr != NULL) *donePtr = 0; + if (totalPtr != NULL) *totalPtr = 0; + + result = NAN; + } + else + { + NSUInteger done = currentWrite->bytesDone; + NSUInteger total = [currentWrite->buffer length]; + + if (tagPtr != NULL) *tagPtr = currentWrite->tag; + if (donePtr != NULL) *donePtr = done; + if (totalPtr != NULL) *totalPtr = total; + + result = (float)done / (float)total; + } + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + return result; +} + +/** + * Conditionally starts a new write. + * + * It is called when: + * - a user requests a write + * - after a write request has finished (to handle the next request) + * - immediately after the socket opens to handle any pending requests + * + * This method also handles auto-disconnect post read/write completion. +**/ +- (void)maybeDequeueWrite +{ + LogTrace(); + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + + + // If we're not currently processing a write AND we have an available write stream + if ((currentWrite == nil) && (flags & kConnected)) + { + if ([writeQueue count] > 0) + { + // Dequeue the next object in the write queue + currentWrite = [writeQueue objectAtIndex:0]; + [writeQueue removeObjectAtIndex:0]; + + + if ([currentWrite isKindOfClass:[GCDAsyncSpecialPacket class]]) + { + LogVerbose(@"Dequeued GCDAsyncSpecialPacket"); + + // Attempt to start TLS + flags |= kStartingWriteTLS; + + // This method won't do anything unless both kStartingReadTLS and kStartingWriteTLS are set + [self maybeStartTLS]; + } + else + { + LogVerbose(@"Dequeued GCDAsyncWritePacket"); + + // Setup write timer (if needed) + [self setupWriteTimerWithTimeout:currentWrite->timeout]; + + // Immediately write, if possible + [self doWriteData]; + } + } + else if (flags & kDisconnectAfterWrites) + { + if (flags & kDisconnectAfterReads) + { + if (([readQueue count] == 0) && (currentRead == nil)) + { + [self closeWithError:nil]; + } + } + else + { + [self closeWithError:nil]; + } + } + } +} + +- (void)doWriteData +{ + LogTrace(); + + // This method is called by the writeSource via the socketQueue + + if ((currentWrite == nil) || (flags & kWritesPaused)) + { + LogVerbose(@"No currentWrite or kWritesPaused"); + + // Unable to write at this time + + if ([self usingCFStreamForTLS]) + { + // CFWriteStream only fires once when there is available data. + // It won't fire again until we've invoked CFWriteStreamWrite. + } + else + { + // If the writeSource is firing, we need to pause it + // or else it will continue to fire over and over again. + + if (flags & kSocketCanAcceptBytes) + { + [self suspendWriteSource]; + } + } + return; + } + + if (!(flags & kSocketCanAcceptBytes)) + { + LogVerbose(@"No space available to write..."); + + // No space available to write. + + if (![self usingCFStreamForTLS]) + { + // Need to wait for writeSource to fire and notify us of + // available space in the socket's internal write buffer. + + [self resumeWriteSource]; + } + return; + } + + if (flags & kStartingWriteTLS) + { + LogVerbose(@"Waiting for SSL/TLS handshake to complete"); + + // The writeQueue is waiting for SSL/TLS handshake to complete. + + if (flags & kStartingReadTLS) + { + if ([self usingSecureTransportForTLS] && lastSSLHandshakeError == errSSLWouldBlock) + { + // We are in the process of a SSL Handshake. + // We were waiting for available space in the socket's internal OS buffer to continue writing. + + [self ssl_continueSSLHandshake]; + } + } + else + { + // We are still waiting for the readQueue to drain and start the SSL/TLS process. + // We now know we can write to the socket. + + if (![self usingCFStreamForTLS]) + { + // Suspend the write source or else it will continue to fire nonstop. + + [self suspendWriteSource]; + } + } + + return; + } + + // Note: This method is not called if currentWrite is a GCDAsyncSpecialPacket (startTLS packet) + + BOOL waiting = NO; + NSError *error = nil; + size_t bytesWritten = 0; + + if (flags & kSocketSecure) + { + if ([self usingCFStreamForTLS]) + { + #if TARGET_OS_IPHONE + + // + // Writing data using CFStream (over internal TLS) + // + + const uint8_t *buffer = (const uint8_t *)[currentWrite->buffer bytes] + currentWrite->bytesDone; + + NSUInteger bytesToWrite = [currentWrite->buffer length] - currentWrite->bytesDone; + + if (bytesToWrite > SIZE_MAX) // NSUInteger may be bigger than size_t (write param 3) + { + bytesToWrite = SIZE_MAX; + } + + CFIndex result = CFWriteStreamWrite(writeStream, buffer, (CFIndex)bytesToWrite); + LogVerbose(@"CFWriteStreamWrite(%lu) = %li", (unsigned long)bytesToWrite, result); + + if (result < 0) + { + error = (__bridge_transfer NSError *)CFWriteStreamCopyError(writeStream); + } + else + { + bytesWritten = (size_t)result; + + // We always set waiting to true in this scenario. + // CFStream may have altered our underlying socket to non-blocking. + // Thus if we attempt to write without a callback, we may end up blocking our queue. + waiting = YES; + } + + #endif + } + else + { + // We're going to use the SSLWrite function. + // + // OSStatus SSLWrite(SSLContextRef context, const void *data, size_t dataLength, size_t *processed) + // + // Parameters: + // context - An SSL session context reference. + // data - A pointer to the buffer of data to write. + // dataLength - The amount, in bytes, of data to write. + // processed - On return, the length, in bytes, of the data actually written. + // + // It sounds pretty straight-forward, + // but there are a few caveats you should be aware of. + // + // The SSLWrite method operates in a non-obvious (and rather annoying) manner. + // According to the documentation: + // + // Because you may configure the underlying connection to operate in a non-blocking manner, + // a write operation might return errSSLWouldBlock, indicating that less data than requested + // was actually transferred. In this case, you should repeat the call to SSLWrite until some + // other result is returned. + // + // This sounds perfect, but when our SSLWriteFunction returns errSSLWouldBlock, + // then the SSLWrite method returns (with the proper errSSLWouldBlock return value), + // but it sets processed to dataLength !! + // + // In other words, if the SSLWrite function doesn't completely write all the data we tell it to, + // then it doesn't tell us how many bytes were actually written. So, for example, if we tell it to + // write 256 bytes then it might actually write 128 bytes, but then report 0 bytes written. + // + // You might be wondering: + // If the SSLWrite function doesn't tell us how many bytes were written, + // then how in the world are we supposed to update our parameters (buffer & bytesToWrite) + // for the next time we invoke SSLWrite? + // + // The answer is that SSLWrite cached all the data we told it to write, + // and it will push out that data next time we call SSLWrite. + // If we call SSLWrite with new data, it will push out the cached data first, and then the new data. + // If we call SSLWrite with empty data, then it will simply push out the cached data. + // + // For this purpose we're going to break large writes into a series of smaller writes. + // This allows us to report progress back to the delegate. + + OSStatus result; + + BOOL hasCachedDataToWrite = (sslWriteCachedLength > 0); + BOOL hasNewDataToWrite = YES; + + if (hasCachedDataToWrite) + { + size_t processed = 0; + + result = SSLWrite(sslContext, NULL, 0, &processed); + + if (result == noErr) + { + bytesWritten = sslWriteCachedLength; + sslWriteCachedLength = 0; + + if ([currentWrite->buffer length] == (currentWrite->bytesDone + bytesWritten)) + { + // We've written all data for the current write. + hasNewDataToWrite = NO; + } + } + else + { + if (result == errSSLWouldBlock) + { + waiting = YES; + } + else + { + error = [self sslError:result]; + } + + // Can't write any new data since we were unable to write the cached data. + hasNewDataToWrite = NO; + } + } + + if (hasNewDataToWrite) + { + const uint8_t *buffer = (const uint8_t *)[currentWrite->buffer bytes] + + currentWrite->bytesDone + + bytesWritten; + + NSUInteger bytesToWrite = [currentWrite->buffer length] - currentWrite->bytesDone - bytesWritten; + + if (bytesToWrite > SIZE_MAX) // NSUInteger may be bigger than size_t (write param 3) + { + bytesToWrite = SIZE_MAX; + } + + size_t bytesRemaining = bytesToWrite; + + BOOL keepLooping = YES; + while (keepLooping) + { + const size_t sslMaxBytesToWrite = 32768; + size_t sslBytesToWrite = MIN(bytesRemaining, sslMaxBytesToWrite); + size_t sslBytesWritten = 0; + + result = SSLWrite(sslContext, buffer, sslBytesToWrite, &sslBytesWritten); + + if (result == noErr) + { + buffer += sslBytesWritten; + bytesWritten += sslBytesWritten; + bytesRemaining -= sslBytesWritten; + + keepLooping = (bytesRemaining > 0); + } + else + { + if (result == errSSLWouldBlock) + { + waiting = YES; + sslWriteCachedLength = sslBytesToWrite; + } + else + { + error = [self sslError:result]; + } + + keepLooping = NO; + } + + } // while (keepLooping) + + } // if (hasNewDataToWrite) + } + } + else + { + // + // Writing data directly over raw socket + // + + int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN; + + const uint8_t *buffer = (const uint8_t *)[currentWrite->buffer bytes] + currentWrite->bytesDone; + + NSUInteger bytesToWrite = [currentWrite->buffer length] - currentWrite->bytesDone; + + if (bytesToWrite > SIZE_MAX) // NSUInteger may be bigger than size_t (write param 3) + { + bytesToWrite = SIZE_MAX; + } + + ssize_t result = write(socketFD, buffer, (size_t)bytesToWrite); + LogVerbose(@"wrote to socket = %zd", result); + + // Check results + if (result < 0) + { + if (errno == EWOULDBLOCK) + { + waiting = YES; + } + else + { + error = [self errnoErrorWithReason:@"Error in write() function"]; + } + } + else + { + bytesWritten = result; + } + } + + // We're done with our writing. + // If we explictly ran into a situation where the socket told us there was no room in the buffer, + // then we immediately resume listening for notifications. + // + // We must do this before we dequeue another write, + // as that may in turn invoke this method again. + // + // Note that if CFStream is involved, it may have maliciously put our socket in blocking mode. + + if (waiting) + { + flags &= ~kSocketCanAcceptBytes; + + if (![self usingCFStreamForTLS]) + { + [self resumeWriteSource]; + } + } + + // Check our results + + BOOL done = NO; + + if (bytesWritten > 0) + { + // Update total amount read for the current write + currentWrite->bytesDone += bytesWritten; + LogVerbose(@"currentWrite->bytesDone = %lu", (unsigned long)currentWrite->bytesDone); + + // Is packet done? + done = (currentWrite->bytesDone == [currentWrite->buffer length]); + } + + if (done) + { + [self completeCurrentWrite]; + + if (!error) + { + dispatch_async(socketQueue, ^{ @autoreleasepool{ + + [self maybeDequeueWrite]; + }}); + } + } + else + { + // We were unable to finish writing the data, + // so we're waiting for another callback to notify us of available space in the lower-level output buffer. + + if (!waiting && !error) + { + // This would be the case if our write was able to accept some data, but not all of it. + + flags &= ~kSocketCanAcceptBytes; + + if (![self usingCFStreamForTLS]) + { + [self resumeWriteSource]; + } + } + + if (bytesWritten > 0) + { + // We're not done with the entire write, but we have written some bytes + + __strong id theDelegate = delegate; + + if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:didWritePartialDataOfLength:tag:)]) + { + long theWriteTag = currentWrite->tag; + + dispatch_async(delegateQueue, ^{ @autoreleasepool { + + [theDelegate socket:self didWritePartialDataOfLength:bytesWritten tag:theWriteTag]; + }}); + } + } + } + + // Check for errors + + if (error) + { + [self closeWithError:[self errnoErrorWithReason:@"Error in write() function"]]; + } + + // Do not add any code here without first adding a return statement in the error case above. +} + +- (void)completeCurrentWrite +{ + LogTrace(); + + NSAssert(currentWrite, @"Trying to complete current write when there is no current write."); + + + __strong id theDelegate = delegate; + + if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:didWriteDataWithTag:)]) + { + long theWriteTag = currentWrite->tag; + + dispatch_async(delegateQueue, ^{ @autoreleasepool { + + [theDelegate socket:self didWriteDataWithTag:theWriteTag]; + }}); + } + + [self endCurrentWrite]; +} + +- (void)endCurrentWrite +{ + if (writeTimer) + { + dispatch_source_cancel(writeTimer); + writeTimer = NULL; + } + + currentWrite = nil; +} + +- (void)setupWriteTimerWithTimeout:(NSTimeInterval)timeout +{ + if (timeout >= 0.0) + { + writeTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, socketQueue); + + __weak GCDAsyncSocket *weakSelf = self; + + dispatch_source_set_event_handler(writeTimer, ^{ @autoreleasepool { + #pragma clang diagnostic push + #pragma clang diagnostic warning "-Wimplicit-retain-self" + + __strong GCDAsyncSocket *strongSelf = weakSelf; + if (strongSelf == nil) return_from_block; + + [strongSelf doWriteTimeout]; + + #pragma clang diagnostic pop + }}); + + #if !OS_OBJECT_USE_OBJC + dispatch_source_t theWriteTimer = writeTimer; + dispatch_source_set_cancel_handler(writeTimer, ^{ + #pragma clang diagnostic push + #pragma clang diagnostic warning "-Wimplicit-retain-self" + + LogVerbose(@"dispatch_release(writeTimer)"); + dispatch_release(theWriteTimer); + + #pragma clang diagnostic pop + }); + #endif + + dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC)); + + dispatch_source_set_timer(writeTimer, tt, DISPATCH_TIME_FOREVER, 0); + dispatch_resume(writeTimer); + } +} + +- (void)doWriteTimeout +{ + // This is a little bit tricky. + // Ideally we'd like to synchronously query the delegate about a timeout extension. + // But if we do so synchronously we risk a possible deadlock. + // So instead we have to do so asynchronously, and callback to ourselves from within the delegate block. + + flags |= kWritesPaused; + + __strong id theDelegate = delegate; + + if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:shouldTimeoutWriteWithTag:elapsed:bytesDone:)]) + { + GCDAsyncWritePacket *theWrite = currentWrite; + + dispatch_async(delegateQueue, ^{ @autoreleasepool { + + NSTimeInterval timeoutExtension = 0.0; + + timeoutExtension = [theDelegate socket:self shouldTimeoutWriteWithTag:theWrite->tag + elapsed:theWrite->timeout + bytesDone:theWrite->bytesDone]; + + dispatch_async(socketQueue, ^{ @autoreleasepool { + + [self doWriteTimeoutWithExtension:timeoutExtension]; + }}); + }}); + } + else + { + [self doWriteTimeoutWithExtension:0.0]; + } +} + +- (void)doWriteTimeoutWithExtension:(NSTimeInterval)timeoutExtension +{ + if (currentWrite) + { + if (timeoutExtension > 0.0) + { + currentWrite->timeout += timeoutExtension; + + // Reschedule the timer + dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeoutExtension * NSEC_PER_SEC)); + dispatch_source_set_timer(writeTimer, tt, DISPATCH_TIME_FOREVER, 0); + + // Unpause writes, and continue + flags &= ~kWritesPaused; + [self doWriteData]; + } + else + { + LogVerbose(@"WriteTimeout"); + + [self closeWithError:[self writeTimeoutError]]; + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Security +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)startTLS:(NSDictionary *)tlsSettings +{ + LogTrace(); + + if (tlsSettings == nil) + { + // Passing nil/NULL to CFReadStreamSetProperty will appear to work the same as passing an empty dictionary, + // but causes problems if we later try to fetch the remote host's certificate. + // + // To be exact, it causes the following to return NULL instead of the normal result: + // CFReadStreamCopyProperty(readStream, kCFStreamPropertySSLPeerCertificates) + // + // So we use an empty dictionary instead, which works perfectly. + + tlsSettings = [NSDictionary dictionary]; + } + + GCDAsyncSpecialPacket *packet = [[GCDAsyncSpecialPacket alloc] initWithTLSSettings:tlsSettings]; + + dispatch_async(socketQueue, ^{ @autoreleasepool { + + if ((flags & kSocketStarted) && !(flags & kQueuedTLS) && !(flags & kForbidReadsWrites)) + { + [readQueue addObject:packet]; + [writeQueue addObject:packet]; + + flags |= kQueuedTLS; + + [self maybeDequeueRead]; + [self maybeDequeueWrite]; + } + }}); + +} + +- (void)maybeStartTLS +{ + // We can't start TLS until: + // - All queued reads prior to the user calling startTLS are complete + // - All queued writes prior to the user calling startTLS are complete + // + // We'll know these conditions are met when both kStartingReadTLS and kStartingWriteTLS are set + + if ((flags & kStartingReadTLS) && (flags & kStartingWriteTLS)) + { + BOOL useSecureTransport = YES; + + #if TARGET_OS_IPHONE + { + GCDAsyncSpecialPacket *tlsPacket = (GCDAsyncSpecialPacket *)currentRead; + NSDictionary *tlsSettings = @{}; + if (tlsPacket) { + tlsSettings = tlsPacket->tlsSettings; + } + NSNumber *value = [tlsSettings objectForKey:GCDAsyncSocketUseCFStreamForTLS]; + if (value && [value boolValue]) + useSecureTransport = NO; + } + #endif + + if (useSecureTransport) + { + [self ssl_startTLS]; + } + else + { + #if TARGET_OS_IPHONE + [self cf_startTLS]; + #endif + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Security via SecureTransport +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (OSStatus)sslReadWithBuffer:(void *)buffer length:(size_t *)bufferLength +{ + LogVerbose(@"sslReadWithBuffer:%p length:%lu", buffer, (unsigned long)*bufferLength); + + if ((socketFDBytesAvailable == 0) && ([sslPreBuffer availableBytes] == 0)) + { + LogVerbose(@"%@ - No data available to read...", THIS_METHOD); + + // No data available to read. + // + // Need to wait for readSource to fire and notify us of + // available data in the socket's internal read buffer. + + [self resumeReadSource]; + + *bufferLength = 0; + return errSSLWouldBlock; + } + + size_t totalBytesRead = 0; + size_t totalBytesLeftToBeRead = *bufferLength; + + BOOL done = NO; + BOOL socketError = NO; + + // + // STEP 1 : READ FROM SSL PRE BUFFER + // + + size_t sslPreBufferLength = [sslPreBuffer availableBytes]; + + if (sslPreBufferLength > 0) + { + LogVerbose(@"%@: Reading from SSL pre buffer...", THIS_METHOD); + + size_t bytesToCopy; + if (sslPreBufferLength > totalBytesLeftToBeRead) + bytesToCopy = totalBytesLeftToBeRead; + else + bytesToCopy = sslPreBufferLength; + + LogVerbose(@"%@: Copying %zu bytes from sslPreBuffer", THIS_METHOD, bytesToCopy); + + memcpy(buffer, [sslPreBuffer readBuffer], bytesToCopy); + [sslPreBuffer didRead:bytesToCopy]; + + LogVerbose(@"%@: sslPreBuffer.length = %zu", THIS_METHOD, [sslPreBuffer availableBytes]); + + totalBytesRead += bytesToCopy; + totalBytesLeftToBeRead -= bytesToCopy; + + done = (totalBytesLeftToBeRead == 0); + + if (done) LogVerbose(@"%@: Complete", THIS_METHOD); + } + + // + // STEP 2 : READ FROM SOCKET + // + + if (!done && (socketFDBytesAvailable > 0)) + { + LogVerbose(@"%@: Reading from socket...", THIS_METHOD); + + int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN; + + BOOL readIntoPreBuffer; + size_t bytesToRead; + uint8_t *buf; + + if (socketFDBytesAvailable > totalBytesLeftToBeRead) + { + // Read all available data from socket into sslPreBuffer. + // Then copy requested amount into dataBuffer. + + LogVerbose(@"%@: Reading into sslPreBuffer...", THIS_METHOD); + + [sslPreBuffer ensureCapacityForWrite:socketFDBytesAvailable]; + + readIntoPreBuffer = YES; + bytesToRead = (size_t)socketFDBytesAvailable; + buf = [sslPreBuffer writeBuffer]; + } + else + { + // Read available data from socket directly into dataBuffer. + + LogVerbose(@"%@: Reading directly into dataBuffer...", THIS_METHOD); + + readIntoPreBuffer = NO; + bytesToRead = totalBytesLeftToBeRead; + buf = (uint8_t *)buffer + totalBytesRead; + } + + ssize_t result = read(socketFD, buf, bytesToRead); + LogVerbose(@"%@: read from socket = %zd", THIS_METHOD, result); + + if (result < 0) + { + LogVerbose(@"%@: read errno = %i", THIS_METHOD, errno); + + if (errno != EWOULDBLOCK) + { + socketError = YES; + } + + socketFDBytesAvailable = 0; + } + else if (result == 0) + { + LogVerbose(@"%@: read EOF", THIS_METHOD); + + socketError = YES; + socketFDBytesAvailable = 0; + } + else + { + size_t bytesReadFromSocket = result; + + if (socketFDBytesAvailable > bytesReadFromSocket) + socketFDBytesAvailable -= bytesReadFromSocket; + else + socketFDBytesAvailable = 0; + + if (readIntoPreBuffer) + { + [sslPreBuffer didWrite:bytesReadFromSocket]; + + size_t bytesToCopy = MIN(totalBytesLeftToBeRead, bytesReadFromSocket); + + LogVerbose(@"%@: Copying %zu bytes out of sslPreBuffer", THIS_METHOD, bytesToCopy); + + memcpy((uint8_t *)buffer + totalBytesRead, [sslPreBuffer readBuffer], bytesToCopy); + [sslPreBuffer didRead:bytesToCopy]; + + totalBytesRead += bytesToCopy; + totalBytesLeftToBeRead -= bytesToCopy; + + LogVerbose(@"%@: sslPreBuffer.length = %zu", THIS_METHOD, [sslPreBuffer availableBytes]); + } + else + { + totalBytesRead += bytesReadFromSocket; + totalBytesLeftToBeRead -= bytesReadFromSocket; + } + + done = (totalBytesLeftToBeRead == 0); + + if (done) LogVerbose(@"%@: Complete", THIS_METHOD); + } + } + + *bufferLength = totalBytesRead; + + if (done) + return noErr; + + if (socketError) + return errSSLClosedAbort; + + return errSSLWouldBlock; +} + +- (OSStatus)sslWriteWithBuffer:(const void *)buffer length:(size_t *)bufferLength +{ + if (!(flags & kSocketCanAcceptBytes)) + { + // Unable to write. + // + // Need to wait for writeSource to fire and notify us of + // available space in the socket's internal write buffer. + + [self resumeWriteSource]; + + *bufferLength = 0; + return errSSLWouldBlock; + } + + size_t bytesToWrite = *bufferLength; + size_t bytesWritten = 0; + + BOOL done = NO; + BOOL socketError = NO; + + int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN; + + ssize_t result = write(socketFD, buffer, bytesToWrite); + + if (result < 0) + { + if (errno != EWOULDBLOCK) + { + socketError = YES; + } + + flags &= ~kSocketCanAcceptBytes; + } + else if (result == 0) + { + flags &= ~kSocketCanAcceptBytes; + } + else + { + bytesWritten = result; + + done = (bytesWritten == bytesToWrite); + } + + *bufferLength = bytesWritten; + + if (done) + return noErr; + + if (socketError) + return errSSLClosedAbort; + + return errSSLWouldBlock; +} + +static OSStatus SSLReadFunction(SSLConnectionRef connection, void *data, size_t *dataLength) +{ + GCDAsyncSocket *asyncSocket = (__bridge GCDAsyncSocket *)connection; + + NSCAssert(dispatch_get_specific(asyncSocket->IsOnSocketQueueOrTargetQueueKey), @"What the deuce?"); + + return [asyncSocket sslReadWithBuffer:data length:dataLength]; +} + +static OSStatus SSLWriteFunction(SSLConnectionRef connection, const void *data, size_t *dataLength) +{ + GCDAsyncSocket *asyncSocket = (__bridge GCDAsyncSocket *)connection; + + NSCAssert(dispatch_get_specific(asyncSocket->IsOnSocketQueueOrTargetQueueKey), @"What the deuce?"); + + return [asyncSocket sslWriteWithBuffer:data length:dataLength]; +} + +- (void)ssl_startTLS +{ + LogTrace(); + + LogVerbose(@"Starting TLS (via SecureTransport)..."); + + OSStatus status; + + GCDAsyncSpecialPacket *tlsPacket = (GCDAsyncSpecialPacket *)currentRead; + if (tlsPacket == nil) // Code to quiet the analyzer + { + NSAssert(NO, @"Logic error"); + + [self closeWithError:[self otherError:@"Logic error"]]; + return; + } + NSDictionary *tlsSettings = tlsPacket->tlsSettings; + + // Create SSLContext, and setup IO callbacks and connection ref + + BOOL isServer = [[tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLIsServer] boolValue]; + + #if TARGET_OS_IPHONE || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) + { + if (isServer) + sslContext = SSLCreateContext(kCFAllocatorDefault, kSSLServerSide, kSSLStreamType); + else + sslContext = SSLCreateContext(kCFAllocatorDefault, kSSLClientSide, kSSLStreamType); + + if (sslContext == NULL) + { + [self closeWithError:[self otherError:@"Error in SSLCreateContext"]]; + return; + } + } + #else // (__MAC_OS_X_VERSION_MIN_REQUIRED < 1080) + { + status = SSLNewContext(isServer, &sslContext); + if (status != noErr) + { + [self closeWithError:[self otherError:@"Error in SSLNewContext"]]; + return; + } + } + #endif + + status = SSLSetIOFuncs(sslContext, &SSLReadFunction, &SSLWriteFunction); + if (status != noErr) + { + [self closeWithError:[self otherError:@"Error in SSLSetIOFuncs"]]; + return; + } + + status = SSLSetConnection(sslContext, (__bridge SSLConnectionRef)self); + if (status != noErr) + { + [self closeWithError:[self otherError:@"Error in SSLSetConnection"]]; + return; + } + + + BOOL shouldManuallyEvaluateTrust = [[tlsSettings objectForKey:GCDAsyncSocketManuallyEvaluateTrust] boolValue]; + if (shouldManuallyEvaluateTrust) + { + if (isServer) + { + [self closeWithError:[self otherError:@"Manual trust validation is not supported for server sockets"]]; + return; + } + + status = SSLSetSessionOption(sslContext, kSSLSessionOptionBreakOnServerAuth, true); + if (status != noErr) + { + [self closeWithError:[self otherError:@"Error in SSLSetSessionOption"]]; + return; + } + + #if !TARGET_OS_IPHONE && (__MAC_OS_X_VERSION_MIN_REQUIRED < 1080) + + // Note from Apple's documentation: + // + // It is only necessary to call SSLSetEnableCertVerify on the Mac prior to OS X 10.8. + // On OS X 10.8 and later setting kSSLSessionOptionBreakOnServerAuth always disables the + // built-in trust evaluation. All versions of iOS behave like OS X 10.8 and thus + // SSLSetEnableCertVerify is not available on that platform at all. + + status = SSLSetEnableCertVerify(sslContext, NO); + if (status != noErr) + { + [self closeWithError:[self otherError:@"Error in SSLSetEnableCertVerify"]]; + return; + } + + #endif + } + + // Configure SSLContext from given settings + // + // Checklist: + // 1. kCFStreamSSLPeerName + // 2. kCFStreamSSLCertificates + // 3. GCDAsyncSocketSSLPeerID + // 4. GCDAsyncSocketSSLProtocolVersionMin + // 5. GCDAsyncSocketSSLProtocolVersionMax + // 6. GCDAsyncSocketSSLSessionOptionFalseStart + // 7. GCDAsyncSocketSSLSessionOptionSendOneByteRecord + // 8. GCDAsyncSocketSSLCipherSuites + // 9. GCDAsyncSocketSSLDiffieHellmanParameters (Mac) + // + // Deprecated (throw error): + // 10. kCFStreamSSLAllowsAnyRoot + // 11. kCFStreamSSLAllowsExpiredRoots + // 12. kCFStreamSSLAllowsExpiredCertificates + // 13. kCFStreamSSLValidatesCertificateChain + // 14. kCFStreamSSLLevel + + id value; + + // 1. kCFStreamSSLPeerName + + value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLPeerName]; + if ([value isKindOfClass:[NSString class]]) + { + NSString *peerName = (NSString *)value; + + const char *peer = [peerName UTF8String]; + size_t peerLen = strlen(peer); + + status = SSLSetPeerDomainName(sslContext, peer, peerLen); + if (status != noErr) + { + [self closeWithError:[self otherError:@"Error in SSLSetPeerDomainName"]]; + return; + } + } + else if (value) + { + NSAssert(NO, @"Invalid value for kCFStreamSSLPeerName. Value must be of type NSString."); + + [self closeWithError:[self otherError:@"Invalid value for kCFStreamSSLPeerName."]]; + return; + } + + // 2. kCFStreamSSLCertificates + + value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLCertificates]; + if ([value isKindOfClass:[NSArray class]]) + { + CFArrayRef certs = (__bridge CFArrayRef)value; + + status = SSLSetCertificate(sslContext, certs); + if (status != noErr) + { + [self closeWithError:[self otherError:@"Error in SSLSetCertificate"]]; + return; + } + } + else if (value) + { + NSAssert(NO, @"Invalid value for kCFStreamSSLCertificates. Value must be of type NSArray."); + + [self closeWithError:[self otherError:@"Invalid value for kCFStreamSSLCertificates."]]; + return; + } + + // 3. GCDAsyncSocketSSLPeerID + + value = [tlsSettings objectForKey:GCDAsyncSocketSSLPeerID]; + if ([value isKindOfClass:[NSData class]]) + { + NSData *peerIdData = (NSData *)value; + + status = SSLSetPeerID(sslContext, [peerIdData bytes], [peerIdData length]); + if (status != noErr) + { + [self closeWithError:[self otherError:@"Error in SSLSetPeerID"]]; + return; + } + } + else if (value) + { + NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLPeerID. Value must be of type NSData." + @" (You can convert strings to data using a method like" + @" [string dataUsingEncoding:NSUTF8StringEncoding])"); + + [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLPeerID."]]; + return; + } + + // 4. GCDAsyncSocketSSLProtocolVersionMin + + value = [tlsSettings objectForKey:GCDAsyncSocketSSLProtocolVersionMin]; + if ([value isKindOfClass:[NSNumber class]]) + { + SSLProtocol minProtocol = (SSLProtocol)[(NSNumber *)value intValue]; + if (minProtocol != kSSLProtocolUnknown) + { + status = SSLSetProtocolVersionMin(sslContext, minProtocol); + if (status != noErr) + { + [self closeWithError:[self otherError:@"Error in SSLSetProtocolVersionMin"]]; + return; + } + } + } + else if (value) + { + NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLProtocolVersionMin. Value must be of type NSNumber."); + + [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLProtocolVersionMin."]]; + return; + } + + // 5. GCDAsyncSocketSSLProtocolVersionMax + + value = [tlsSettings objectForKey:GCDAsyncSocketSSLProtocolVersionMax]; + if ([value isKindOfClass:[NSNumber class]]) + { + SSLProtocol maxProtocol = (SSLProtocol)[(NSNumber *)value intValue]; + if (maxProtocol != kSSLProtocolUnknown) + { + status = SSLSetProtocolVersionMax(sslContext, maxProtocol); + if (status != noErr) + { + [self closeWithError:[self otherError:@"Error in SSLSetProtocolVersionMax"]]; + return; + } + } + } + else if (value) + { + NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLProtocolVersionMax. Value must be of type NSNumber."); + + [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLProtocolVersionMax."]]; + return; + } + + // 6. GCDAsyncSocketSSLSessionOptionFalseStart + + value = [tlsSettings objectForKey:GCDAsyncSocketSSLSessionOptionFalseStart]; + if ([value isKindOfClass:[NSNumber class]]) + { + status = SSLSetSessionOption(sslContext, kSSLSessionOptionFalseStart, [value boolValue]); + if (status != noErr) + { + [self closeWithError:[self otherError:@"Error in SSLSetSessionOption (kSSLSessionOptionFalseStart)"]]; + return; + } + } + else if (value) + { + NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLSessionOptionFalseStart. Value must be of type NSNumber."); + + [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLSessionOptionFalseStart."]]; + return; + } + + // 7. GCDAsyncSocketSSLSessionOptionSendOneByteRecord + + value = [tlsSettings objectForKey:GCDAsyncSocketSSLSessionOptionSendOneByteRecord]; + if ([value isKindOfClass:[NSNumber class]]) + { + status = SSLSetSessionOption(sslContext, kSSLSessionOptionSendOneByteRecord, [value boolValue]); + if (status != noErr) + { + [self closeWithError: + [self otherError:@"Error in SSLSetSessionOption (kSSLSessionOptionSendOneByteRecord)"]]; + return; + } + } + else if (value) + { + NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLSessionOptionSendOneByteRecord." + @" Value must be of type NSNumber."); + + [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLSessionOptionSendOneByteRecord."]]; + return; + } + + // 8. GCDAsyncSocketSSLCipherSuites + + value = [tlsSettings objectForKey:GCDAsyncSocketSSLCipherSuites]; + if ([value isKindOfClass:[NSArray class]]) + { + NSArray *cipherSuites = (NSArray *)value; + NSUInteger numberCiphers = [cipherSuites count]; + SSLCipherSuite ciphers[numberCiphers]; + + NSUInteger cipherIndex; + for (cipherIndex = 0; cipherIndex < numberCiphers; cipherIndex++) + { + NSNumber *cipherObject = [cipherSuites objectAtIndex:cipherIndex]; + ciphers[cipherIndex] = [cipherObject shortValue]; + } + + status = SSLSetEnabledCiphers(sslContext, ciphers, numberCiphers); + if (status != noErr) + { + [self closeWithError:[self otherError:@"Error in SSLSetEnabledCiphers"]]; + return; + } + } + else if (value) + { + NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLCipherSuites. Value must be of type NSArray."); + + [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLCipherSuites."]]; + return; + } + + // 9. GCDAsyncSocketSSLDiffieHellmanParameters + + #if !TARGET_OS_IPHONE + value = [tlsSettings objectForKey:GCDAsyncSocketSSLDiffieHellmanParameters]; + if ([value isKindOfClass:[NSData class]]) + { + NSData *diffieHellmanData = (NSData *)value; + + status = SSLSetDiffieHellmanParams(sslContext, [diffieHellmanData bytes], [diffieHellmanData length]); + if (status != noErr) + { + [self closeWithError:[self otherError:@"Error in SSLSetDiffieHellmanParams"]]; + return; + } + } + else if (value) + { + NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLDiffieHellmanParameters. Value must be of type NSData."); + + [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLDiffieHellmanParameters."]]; + return; + } + #endif + + // DEPRECATED checks + + // 10. kCFStreamSSLAllowsAnyRoot + + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wdeprecated-declarations" + value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLAllowsAnyRoot]; + #pragma clang diagnostic pop + if (value) + { + NSAssert(NO, @"Security option unavailable - kCFStreamSSLAllowsAnyRoot" + @" - You must use manual trust evaluation"); + + [self closeWithError:[self otherError:@"Security option unavailable - kCFStreamSSLAllowsAnyRoot"]]; + return; + } + + // 11. kCFStreamSSLAllowsExpiredRoots + + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wdeprecated-declarations" + value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLAllowsExpiredRoots]; + #pragma clang diagnostic pop + if (value) + { + NSAssert(NO, @"Security option unavailable - kCFStreamSSLAllowsExpiredRoots" + @" - You must use manual trust evaluation"); + + [self closeWithError:[self otherError:@"Security option unavailable - kCFStreamSSLAllowsExpiredRoots"]]; + return; + } + + // 12. kCFStreamSSLValidatesCertificateChain + + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wdeprecated-declarations" + value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLValidatesCertificateChain]; + #pragma clang diagnostic pop + if (value) + { + NSAssert(NO, @"Security option unavailable - kCFStreamSSLValidatesCertificateChain" + @" - You must use manual trust evaluation"); + + [self closeWithError:[self otherError:@"Security option unavailable - kCFStreamSSLValidatesCertificateChain"]]; + return; + } + + // 13. kCFStreamSSLAllowsExpiredCertificates + + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wdeprecated-declarations" + value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLAllowsExpiredCertificates]; + #pragma clang diagnostic pop + if (value) + { + NSAssert(NO, @"Security option unavailable - kCFStreamSSLAllowsExpiredCertificates" + @" - You must use manual trust evaluation"); + + [self closeWithError:[self otherError:@"Security option unavailable - kCFStreamSSLAllowsExpiredCertificates"]]; + return; + } + + // 14. kCFStreamSSLLevel + + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wdeprecated-declarations" + value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLLevel]; + #pragma clang diagnostic pop + if (value) + { + NSAssert(NO, @"Security option unavailable - kCFStreamSSLLevel" + @" - You must use GCDAsyncSocketSSLProtocolVersionMin & GCDAsyncSocketSSLProtocolVersionMax"); + + [self closeWithError:[self otherError:@"Security option unavailable - kCFStreamSSLLevel"]]; + return; + } + + // Setup the sslPreBuffer + // + // Any data in the preBuffer needs to be moved into the sslPreBuffer, + // as this data is now part of the secure read stream. + + sslPreBuffer = [[GCDAsyncSocketPreBuffer alloc] initWithCapacity:(1024 * 4)]; + + size_t preBufferLength = [preBuffer availableBytes]; + + if (preBufferLength > 0) + { + [sslPreBuffer ensureCapacityForWrite:preBufferLength]; + + memcpy([sslPreBuffer writeBuffer], [preBuffer readBuffer], preBufferLength); + [preBuffer didRead:preBufferLength]; + [sslPreBuffer didWrite:preBufferLength]; + } + + sslErrCode = lastSSLHandshakeError = noErr; + + // Start the SSL Handshake process + + [self ssl_continueSSLHandshake]; +} + +- (void)ssl_continueSSLHandshake +{ + LogTrace(); + + // If the return value is noErr, the session is ready for normal secure communication. + // If the return value is errSSLWouldBlock, the SSLHandshake function must be called again. + // If the return value is errSSLServerAuthCompleted, we ask delegate if we should trust the + // server and then call SSLHandshake again to resume the handshake or close the connection + // errSSLPeerBadCert SSL error. + // Otherwise, the return value indicates an error code. + + OSStatus status = SSLHandshake(sslContext); + lastSSLHandshakeError = status; + + if (status == noErr) + { + LogVerbose(@"SSLHandshake complete"); + + flags &= ~kStartingReadTLS; + flags &= ~kStartingWriteTLS; + + flags |= kSocketSecure; + + __strong id theDelegate = delegate; + + if (delegateQueue && [theDelegate respondsToSelector:@selector(socketDidSecure:)]) + { + dispatch_async(delegateQueue, ^{ @autoreleasepool { + + [theDelegate socketDidSecure:self]; + }}); + } + + [self endCurrentRead]; + [self endCurrentWrite]; + + [self maybeDequeueRead]; + [self maybeDequeueWrite]; + } + else if (status == errSSLPeerAuthCompleted) + { + LogVerbose(@"SSLHandshake peerAuthCompleted - awaiting delegate approval"); + + __block SecTrustRef trust = NULL; + status = SSLCopyPeerTrust(sslContext, &trust); + if (status != noErr) + { + [self closeWithError:[self sslError:status]]; + return; + } + + int aStateIndex = stateIndex; + dispatch_queue_t theSocketQueue = socketQueue; + + __weak GCDAsyncSocket *weakSelf = self; + + void (^comletionHandler)(BOOL) = ^(BOOL shouldTrust){ @autoreleasepool { + #pragma clang diagnostic push + #pragma clang diagnostic warning "-Wimplicit-retain-self" + + dispatch_async(theSocketQueue, ^{ @autoreleasepool { + + if (trust) { + CFRelease(trust); + trust = NULL; + } + + __strong GCDAsyncSocket *strongSelf = weakSelf; + if (strongSelf) + { + [strongSelf ssl_shouldTrustPeer:shouldTrust stateIndex:aStateIndex]; + } + }}); + + #pragma clang diagnostic pop + }}; + + __strong id theDelegate = delegate; + + if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:didReceiveTrust:completionHandler:)]) + { + dispatch_async(delegateQueue, ^{ @autoreleasepool { + + [theDelegate socket:self didReceiveTrust:trust completionHandler:comletionHandler]; + }}); + } + else + { + if (trust) { + CFRelease(trust); + trust = NULL; + } + + NSString *msg = @"GCDAsyncSocketManuallyEvaluateTrust specified in tlsSettings," + @" but delegate doesn't implement socket:shouldTrustPeer:"; + + [self closeWithError:[self otherError:msg]]; + return; + } + } + else if (status == errSSLWouldBlock) + { + LogVerbose(@"SSLHandshake continues..."); + + // Handshake continues... + // + // This method will be called again from doReadData or doWriteData. + } + else + { + [self closeWithError:[self sslError:status]]; + } +} + +- (void)ssl_shouldTrustPeer:(BOOL)shouldTrust stateIndex:(int)aStateIndex +{ + LogTrace(); + + if (aStateIndex != stateIndex) + { + LogInfo(@"Ignoring ssl_shouldTrustPeer - invalid state (maybe disconnected)"); + + // One of the following is true + // - the socket was disconnected + // - the startTLS operation timed out + // - the completionHandler was already invoked once + + return; + } + + // Increment stateIndex to ensure completionHandler can only be called once. + stateIndex++; + + if (shouldTrust) + { + NSAssert(lastSSLHandshakeError == errSSLPeerAuthCompleted, @"ssl_shouldTrustPeer called when last error is %d and not errSSLPeerAuthCompleted", (int)lastSSLHandshakeError); + [self ssl_continueSSLHandshake]; + } + else + { + [self closeWithError:[self sslError:errSSLPeerBadCert]]; + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Security via CFStream +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#if TARGET_OS_IPHONE + +- (void)cf_finishSSLHandshake +{ + LogTrace(); + + if ((flags & kStartingReadTLS) && (flags & kStartingWriteTLS)) + { + flags &= ~kStartingReadTLS; + flags &= ~kStartingWriteTLS; + + flags |= kSocketSecure; + + __strong id theDelegate = delegate; + + if (delegateQueue && [theDelegate respondsToSelector:@selector(socketDidSecure:)]) + { + dispatch_async(delegateQueue, ^{ @autoreleasepool { + + [theDelegate socketDidSecure:self]; + }}); + } + + [self endCurrentRead]; + [self endCurrentWrite]; + + [self maybeDequeueRead]; + [self maybeDequeueWrite]; + } +} + +- (void)cf_abortSSLHandshake:(NSError *)error +{ + LogTrace(); + + if ((flags & kStartingReadTLS) && (flags & kStartingWriteTLS)) + { + flags &= ~kStartingReadTLS; + flags &= ~kStartingWriteTLS; + + [self closeWithError:error]; + } +} + +- (void)cf_startTLS +{ + LogTrace(); + + LogVerbose(@"Starting TLS (via CFStream)..."); + + if ([preBuffer availableBytes] > 0) + { + NSString *msg = @"Invalid TLS transition. Handshake has already been read from socket."; + + [self closeWithError:[self otherError:msg]]; + return; + } + + [self suspendReadSource]; + [self suspendWriteSource]; + + socketFDBytesAvailable = 0; + flags &= ~kSocketCanAcceptBytes; + flags &= ~kSecureSocketHasBytesAvailable; + + flags |= kUsingCFStreamForTLS; + + if (![self createReadAndWriteStream]) + { + [self closeWithError:[self otherError:@"Error in CFStreamCreatePairWithSocket"]]; + return; + } + + if (![self registerForStreamCallbacksIncludingReadWrite:YES]) + { + [self closeWithError:[self otherError:@"Error in CFStreamSetClient"]]; + return; + } + + if (![self addStreamsToRunLoop]) + { + [self closeWithError:[self otherError:@"Error in CFStreamScheduleWithRunLoop"]]; + return; + } + + NSAssert([currentRead isKindOfClass:[GCDAsyncSpecialPacket class]], @"Invalid read packet for startTLS"); + NSAssert([currentWrite isKindOfClass:[GCDAsyncSpecialPacket class]], @"Invalid write packet for startTLS"); + + GCDAsyncSpecialPacket *tlsPacket = (GCDAsyncSpecialPacket *)currentRead; + CFDictionaryRef tlsSettings = (__bridge CFDictionaryRef)tlsPacket->tlsSettings; + + // Getting an error concerning kCFStreamPropertySSLSettings ? + // You need to add the CFNetwork framework to your iOS application. + + BOOL r1 = CFReadStreamSetProperty(readStream, kCFStreamPropertySSLSettings, tlsSettings); + BOOL r2 = CFWriteStreamSetProperty(writeStream, kCFStreamPropertySSLSettings, tlsSettings); + + // For some reason, starting around the time of iOS 4.3, + // the first call to set the kCFStreamPropertySSLSettings will return true, + // but the second will return false. + // + // Order doesn't seem to matter. + // So you could call CFReadStreamSetProperty and then CFWriteStreamSetProperty, or you could reverse the order. + // Either way, the first call will return true, and the second returns false. + // + // Interestingly, this doesn't seem to affect anything. + // Which is not altogether unusual, as the documentation seems to suggest that (for many settings) + // setting it on one side of the stream automatically sets it for the other side of the stream. + // + // Although there isn't anything in the documentation to suggest that the second attempt would fail. + // + // Furthermore, this only seems to affect streams that are negotiating a security upgrade. + // In other words, the socket gets connected, there is some back-and-forth communication over the unsecure + // connection, and then a startTLS is issued. + // So this mostly affects newer protocols (XMPP, IMAP) as opposed to older protocols (HTTPS). + + if (!r1 && !r2) // Yes, the && is correct - workaround for apple bug. + { + [self closeWithError:[self otherError:@"Error in CFStreamSetProperty"]]; + return; + } + + if (![self openStreams]) + { + [self closeWithError:[self otherError:@"Error in CFStreamOpen"]]; + return; + } + + LogVerbose(@"Waiting for SSL Handshake to complete..."); +} + +#endif + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark CFStream +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#if TARGET_OS_IPHONE + ++ (void)ignore:(id)_ +{} + ++ (void)startCFStreamThreadIfNeeded +{ + LogTrace(); + + static dispatch_once_t predicate; + dispatch_once(&predicate, ^{ + + cfstreamThreadRetainCount = 0; + cfstreamThreadSetupQueue = dispatch_queue_create("GCDAsyncSocket-CFStreamThreadSetup", DISPATCH_QUEUE_SERIAL); + }); + + dispatch_sync(cfstreamThreadSetupQueue, ^{ @autoreleasepool { + + if (++cfstreamThreadRetainCount == 1) + { + cfstreamThread = [[NSThread alloc] initWithTarget:self + selector:@selector(cfstreamThread) + object:nil]; + [cfstreamThread start]; + } + }}); +} + ++ (void)stopCFStreamThreadIfNeeded +{ + LogTrace(); + + // The creation of the cfstreamThread is relatively expensive. + // So we'd like to keep it available for recycling. + // However, there's a tradeoff here, because it shouldn't remain alive forever. + // So what we're going to do is use a little delay before taking it down. + // This way it can be reused properly in situations where multiple sockets are continually in flux. + + int delayInSeconds = 30; + dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); + dispatch_after(when, cfstreamThreadSetupQueue, ^{ @autoreleasepool { + #pragma clang diagnostic push + #pragma clang diagnostic warning "-Wimplicit-retain-self" + + if (cfstreamThreadRetainCount == 0) + { + LogWarn(@"Logic error concerning cfstreamThread start / stop"); + return_from_block; + } + + if (--cfstreamThreadRetainCount == 0) + { + [cfstreamThread cancel]; // set isCancelled flag + + // wake up the thread + [[self class] performSelector:@selector(ignore:) + onThread:cfstreamThread + withObject:[NSNull null] + waitUntilDone:NO]; + + cfstreamThread = nil; + } + + #pragma clang diagnostic pop + }}); +} + ++ (void)cfstreamThread { @autoreleasepool +{ + [[NSThread currentThread] setName:GCDAsyncSocketThreadName]; + + LogInfo(@"CFStreamThread: Started"); + + // We can't run the run loop unless it has an associated input source or a timer. + // So we'll just create a timer that will never fire - unless the server runs for decades. + [NSTimer scheduledTimerWithTimeInterval:[[NSDate distantFuture] timeIntervalSinceNow] + target:self + selector:@selector(ignore:) + userInfo:nil + repeats:YES]; + + NSThread *currentThread = [NSThread currentThread]; + NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop]; + + BOOL isCancelled = [currentThread isCancelled]; + + while (!isCancelled && [currentRunLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]) + { + isCancelled = [currentThread isCancelled]; + } + + LogInfo(@"CFStreamThread: Stopped"); +}} + ++ (void)scheduleCFStreams:(GCDAsyncSocket *)asyncSocket +{ + LogTrace(); + NSAssert([NSThread currentThread] == cfstreamThread, @"Invoked on wrong thread"); + + CFRunLoopRef runLoop = CFRunLoopGetCurrent(); + + if (asyncSocket->readStream) + CFReadStreamScheduleWithRunLoop(asyncSocket->readStream, runLoop, kCFRunLoopDefaultMode); + + if (asyncSocket->writeStream) + CFWriteStreamScheduleWithRunLoop(asyncSocket->writeStream, runLoop, kCFRunLoopDefaultMode); +} + ++ (void)unscheduleCFStreams:(GCDAsyncSocket *)asyncSocket +{ + LogTrace(); + NSAssert([NSThread currentThread] == cfstreamThread, @"Invoked on wrong thread"); + + CFRunLoopRef runLoop = CFRunLoopGetCurrent(); + + if (asyncSocket->readStream) + CFReadStreamUnscheduleFromRunLoop(asyncSocket->readStream, runLoop, kCFRunLoopDefaultMode); + + if (asyncSocket->writeStream) + CFWriteStreamUnscheduleFromRunLoop(asyncSocket->writeStream, runLoop, kCFRunLoopDefaultMode); +} + +static void CFReadStreamCallback (CFReadStreamRef stream, CFStreamEventType type, void *pInfo) +{ + GCDAsyncSocket *asyncSocket = (__bridge GCDAsyncSocket *)pInfo; + + switch(type) + { + case kCFStreamEventHasBytesAvailable: + { + dispatch_async(asyncSocket->socketQueue, ^{ @autoreleasepool { + + LogCVerbose(@"CFReadStreamCallback - HasBytesAvailable"); + + if (asyncSocket->readStream != stream) + return_from_block; + + if ((asyncSocket->flags & kStartingReadTLS) && (asyncSocket->flags & kStartingWriteTLS)) + { + // If we set kCFStreamPropertySSLSettings before we opened the streams, this might be a lie. + // (A callback related to the tcp stream, but not to the SSL layer). + + if (CFReadStreamHasBytesAvailable(asyncSocket->readStream)) + { + asyncSocket->flags |= kSecureSocketHasBytesAvailable; + [asyncSocket cf_finishSSLHandshake]; + } + } + else + { + asyncSocket->flags |= kSecureSocketHasBytesAvailable; + [asyncSocket doReadData]; + } + }}); + + break; + } + default: + { + NSError *error = (__bridge_transfer NSError *)CFReadStreamCopyError(stream); + + if (error == nil && type == kCFStreamEventEndEncountered) + { + error = [asyncSocket connectionClosedError]; + } + + dispatch_async(asyncSocket->socketQueue, ^{ @autoreleasepool { + + LogCVerbose(@"CFReadStreamCallback - Other"); + + if (asyncSocket->readStream != stream) + return_from_block; + + if ((asyncSocket->flags & kStartingReadTLS) && (asyncSocket->flags & kStartingWriteTLS)) + { + [asyncSocket cf_abortSSLHandshake:error]; + } + else + { + [asyncSocket closeWithError:error]; + } + }}); + + break; + } + } + +} + +static void CFWriteStreamCallback (CFWriteStreamRef stream, CFStreamEventType type, void *pInfo) +{ + GCDAsyncSocket *asyncSocket = (__bridge GCDAsyncSocket *)pInfo; + + switch(type) + { + case kCFStreamEventCanAcceptBytes: + { + dispatch_async(asyncSocket->socketQueue, ^{ @autoreleasepool { + + LogCVerbose(@"CFWriteStreamCallback - CanAcceptBytes"); + + if (asyncSocket->writeStream != stream) + return_from_block; + + if ((asyncSocket->flags & kStartingReadTLS) && (asyncSocket->flags & kStartingWriteTLS)) + { + // If we set kCFStreamPropertySSLSettings before we opened the streams, this might be a lie. + // (A callback related to the tcp stream, but not to the SSL layer). + + if (CFWriteStreamCanAcceptBytes(asyncSocket->writeStream)) + { + asyncSocket->flags |= kSocketCanAcceptBytes; + [asyncSocket cf_finishSSLHandshake]; + } + } + else + { + asyncSocket->flags |= kSocketCanAcceptBytes; + [asyncSocket doWriteData]; + } + }}); + + break; + } + default: + { + NSError *error = (__bridge_transfer NSError *)CFWriteStreamCopyError(stream); + + if (error == nil && type == kCFStreamEventEndEncountered) + { + error = [asyncSocket connectionClosedError]; + } + + dispatch_async(asyncSocket->socketQueue, ^{ @autoreleasepool { + + LogCVerbose(@"CFWriteStreamCallback - Other"); + + if (asyncSocket->writeStream != stream) + return_from_block; + + if ((asyncSocket->flags & kStartingReadTLS) && (asyncSocket->flags & kStartingWriteTLS)) + { + [asyncSocket cf_abortSSLHandshake:error]; + } + else + { + [asyncSocket closeWithError:error]; + } + }}); + + break; + } + } + +} + +- (BOOL)createReadAndWriteStream +{ + LogTrace(); + + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + + + if (readStream || writeStream) + { + // Streams already created + return YES; + } + + int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN; + + if (socketFD == SOCKET_NULL) + { + // Cannot create streams without a file descriptor + return NO; + } + + if (![self isConnected]) + { + // Cannot create streams until file descriptor is connected + return NO; + } + + LogVerbose(@"Creating read and write stream..."); + + CFStreamCreatePairWithSocket(NULL, (CFSocketNativeHandle)socketFD, &readStream, &writeStream); + + // The kCFStreamPropertyShouldCloseNativeSocket property should be false by default (for our case). + // But let's not take any chances. + + if (readStream) + CFReadStreamSetProperty(readStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanFalse); + if (writeStream) + CFWriteStreamSetProperty(writeStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanFalse); + + if ((readStream == NULL) || (writeStream == NULL)) + { + LogWarn(@"Unable to create read and write stream..."); + + if (readStream) + { + CFReadStreamClose(readStream); + CFRelease(readStream); + readStream = NULL; + } + if (writeStream) + { + CFWriteStreamClose(writeStream); + CFRelease(writeStream); + writeStream = NULL; + } + + return NO; + } + + return YES; +} + +- (BOOL)registerForStreamCallbacksIncludingReadWrite:(BOOL)includeReadWrite +{ + LogVerbose(@"%@ %@", THIS_METHOD, (includeReadWrite ? @"YES" : @"NO")); + + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + NSAssert((readStream != NULL && writeStream != NULL), @"Read/Write stream is null"); + + streamContext.version = 0; + streamContext.info = (__bridge void *)(self); + streamContext.retain = nil; + streamContext.release = nil; + streamContext.copyDescription = nil; + + CFOptionFlags readStreamEvents = kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered; + if (includeReadWrite) + readStreamEvents |= kCFStreamEventHasBytesAvailable; + + if (!CFReadStreamSetClient(readStream, readStreamEvents, &CFReadStreamCallback, &streamContext)) + { + return NO; + } + + CFOptionFlags writeStreamEvents = kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered; + if (includeReadWrite) + writeStreamEvents |= kCFStreamEventCanAcceptBytes; + + if (!CFWriteStreamSetClient(writeStream, writeStreamEvents, &CFWriteStreamCallback, &streamContext)) + { + return NO; + } + + return YES; +} + +- (BOOL)addStreamsToRunLoop +{ + LogTrace(); + + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + NSAssert((readStream != NULL && writeStream != NULL), @"Read/Write stream is null"); + + if (!(flags & kAddedStreamsToRunLoop)) + { + LogVerbose(@"Adding streams to runloop..."); + + [[self class] startCFStreamThreadIfNeeded]; + [[self class] performSelector:@selector(scheduleCFStreams:) + onThread:cfstreamThread + withObject:self + waitUntilDone:YES]; + + flags |= kAddedStreamsToRunLoop; + } + + return YES; +} + +- (void)removeStreamsFromRunLoop +{ + LogTrace(); + + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + NSAssert((readStream != NULL && writeStream != NULL), @"Read/Write stream is null"); + + if (flags & kAddedStreamsToRunLoop) + { + LogVerbose(@"Removing streams from runloop..."); + + [[self class] performSelector:@selector(unscheduleCFStreams:) + onThread:cfstreamThread + withObject:self + waitUntilDone:YES]; + [[self class] stopCFStreamThreadIfNeeded]; + + flags &= ~kAddedStreamsToRunLoop; + } +} + +- (BOOL)openStreams +{ + LogTrace(); + + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + NSAssert((readStream != NULL && writeStream != NULL), @"Read/Write stream is null"); + + CFStreamStatus readStatus = CFReadStreamGetStatus(readStream); + CFStreamStatus writeStatus = CFWriteStreamGetStatus(writeStream); + + if ((readStatus == kCFStreamStatusNotOpen) || (writeStatus == kCFStreamStatusNotOpen)) + { + LogVerbose(@"Opening read and write stream..."); + + BOOL r1 = CFReadStreamOpen(readStream); + BOOL r2 = CFWriteStreamOpen(writeStream); + + if (!r1 || !r2) + { + LogError(@"Error in CFStreamOpen"); + return NO; + } + } + + return YES; +} + +#endif + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Advanced +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * See header file for big discussion of this method. +**/ +- (BOOL)autoDisconnectOnClosedReadStream +{ + // Note: YES means kAllowHalfDuplexConnection is OFF + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + return ((config & kAllowHalfDuplexConnection) == 0); + } + else + { + __block BOOL result; + + dispatch_sync(socketQueue, ^{ + result = ((config & kAllowHalfDuplexConnection) == 0); + }); + + return result; + } +} + +/** + * See header file for big discussion of this method. +**/ +- (void)setAutoDisconnectOnClosedReadStream:(BOOL)flag +{ + // Note: YES means kAllowHalfDuplexConnection is OFF + + dispatch_block_t block = ^{ + + if (flag) + config &= ~kAllowHalfDuplexConnection; + else + config |= kAllowHalfDuplexConnection; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_async(socketQueue, block); +} + + +/** + * See header file for big discussion of this method. +**/ +- (void)markSocketQueueTargetQueue:(dispatch_queue_t)socketNewTargetQueue +{ + void *nonNullUnusedPointer = (__bridge void *)self; + dispatch_queue_set_specific(socketNewTargetQueue, IsOnSocketQueueOrTargetQueueKey, nonNullUnusedPointer, NULL); +} + +/** + * See header file for big discussion of this method. +**/ +- (void)unmarkSocketQueueTargetQueue:(dispatch_queue_t)socketOldTargetQueue +{ + dispatch_queue_set_specific(socketOldTargetQueue, IsOnSocketQueueOrTargetQueueKey, NULL, NULL); +} + +/** + * See header file for big discussion of this method. +**/ +- (void)performBlock:(dispatch_block_t)block +{ + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); +} + +/** + * Questions? Have you read the header file? +**/ +- (int)socketFD +{ + if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD); + return SOCKET_NULL; + } + + if (socket4FD != SOCKET_NULL) + return socket4FD; + else + return socket6FD; +} + +/** + * Questions? Have you read the header file? +**/ +- (int)socket4FD +{ + if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD); + return SOCKET_NULL; + } + + return socket4FD; +} + +/** + * Questions? Have you read the header file? +**/ +- (int)socket6FD +{ + if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD); + return SOCKET_NULL; + } + + return socket6FD; +} + +#if TARGET_OS_IPHONE + +/** + * Questions? Have you read the header file? +**/ +- (CFReadStreamRef)readStream +{ + if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD); + return NULL; + } + + if (readStream == NULL) + [self createReadAndWriteStream]; + + return readStream; +} + +/** + * Questions? Have you read the header file? +**/ +- (CFWriteStreamRef)writeStream +{ + if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD); + return NULL; + } + + if (writeStream == NULL) + [self createReadAndWriteStream]; + + return writeStream; +} + +- (BOOL)enableBackgroundingOnSocketWithCaveat:(BOOL)caveat +{ + if (![self createReadAndWriteStream]) + { + // Error occurred creating streams (perhaps socket isn't open) + return NO; + } + + BOOL r1, r2; + + LogVerbose(@"Enabling backgrouding on socket"); + + r1 = CFReadStreamSetProperty(readStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); + r2 = CFWriteStreamSetProperty(writeStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); + + if (!r1 || !r2) + { + return NO; + } + + if (!caveat) + { + if (![self openStreams]) + { + return NO; + } + } + + return YES; +} + +/** + * Questions? Have you read the header file? +**/ +- (BOOL)enableBackgroundingOnSocket +{ + LogTrace(); + + if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD); + return NO; + } + + return [self enableBackgroundingOnSocketWithCaveat:NO]; +} + +- (BOOL)enableBackgroundingOnSocketWithCaveat // Deprecated in iOS 4.??? +{ + // This method was created as a workaround for a bug in iOS. + // Apple has since fixed this bug. + // I'm not entirely sure which version of iOS they fixed it in... + + LogTrace(); + + if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD); + return NO; + } + + return [self enableBackgroundingOnSocketWithCaveat:YES]; +} + +#endif + +- (SSLContextRef)sslContext +{ + if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD); + return NULL; + } + + return sslContext; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Class Utilities +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + ++ (NSMutableArray *)lookupHost:(NSString *)host port:(uint16_t)port error:(NSError **)errPtr +{ + LogTrace(); + + NSMutableArray *addresses = nil; + NSError *error = nil; + + if ([host isEqualToString:@"localhost"] || [host isEqualToString:@"loopback"]) + { + // Use LOOPBACK address + struct sockaddr_in nativeAddr4; + nativeAddr4.sin_len = sizeof(struct sockaddr_in); + nativeAddr4.sin_family = AF_INET; + nativeAddr4.sin_port = htons(port); + nativeAddr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + memset(&(nativeAddr4.sin_zero), 0, sizeof(nativeAddr4.sin_zero)); + + struct sockaddr_in6 nativeAddr6; + nativeAddr6.sin6_len = sizeof(struct sockaddr_in6); + nativeAddr6.sin6_family = AF_INET6; + nativeAddr6.sin6_port = htons(port); + nativeAddr6.sin6_flowinfo = 0; + nativeAddr6.sin6_addr = in6addr_loopback; + nativeAddr6.sin6_scope_id = 0; + + // Wrap the native address structures + + NSData *address4 = [NSData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)]; + NSData *address6 = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)]; + + addresses = [NSMutableArray arrayWithCapacity:2]; + [addresses addObject:address4]; + [addresses addObject:address6]; + } + else + { + NSString *portStr = [NSString stringWithFormat:@"%hu", port]; + + struct addrinfo hints, *res, *res0; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + + int gai_error = getaddrinfo([host UTF8String], [portStr UTF8String], &hints, &res0); + + if (gai_error) + { + error = [self gaiError:gai_error]; + } + else + { + NSUInteger capacity = 0; + for (res = res0; res; res = res->ai_next) + { + if (res->ai_family == AF_INET || res->ai_family == AF_INET6) { + capacity++; + } + } + + addresses = [NSMutableArray arrayWithCapacity:capacity]; + + for (res = res0; res; res = res->ai_next) + { + if (res->ai_family == AF_INET) + { + // Found IPv4 address. + // Wrap the native address structure, and add to results. + + NSData *address4 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]; + [addresses addObject:address4]; + } + else if (res->ai_family == AF_INET6) + { + // Fixes connection issues with IPv6 + // https://github.com/robbiehanson/CocoaAsyncSocket/issues/429#issuecomment-222477158 + + // Found IPv6 address. + // Wrap the native address structure, and add to results. + + struct sockaddr_in6 *sockaddr = (struct sockaddr_in6 *)res->ai_addr; + in_port_t *portPtr = &sockaddr->sin6_port; + if ((portPtr != NULL) && (*portPtr == 0)) { + *portPtr = htons(port); + } + + NSData *address6 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]; + [addresses addObject:address6]; + } + } + freeaddrinfo(res0); + + if ([addresses count] == 0) + { + error = [self gaiError:EAI_FAIL]; + } + } + } + + if (errPtr) *errPtr = error; + return addresses; +} + ++ (NSString *)hostFromSockaddr4:(const struct sockaddr_in *)pSockaddr4 +{ + char addrBuf[INET_ADDRSTRLEN]; + + if (inet_ntop(AF_INET, &pSockaddr4->sin_addr, addrBuf, (socklen_t)sizeof(addrBuf)) == NULL) + { + addrBuf[0] = '\0'; + } + + return [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding]; +} + ++ (NSString *)hostFromSockaddr6:(const struct sockaddr_in6 *)pSockaddr6 +{ + char addrBuf[INET6_ADDRSTRLEN]; + + if (inet_ntop(AF_INET6, &pSockaddr6->sin6_addr, addrBuf, (socklen_t)sizeof(addrBuf)) == NULL) + { + addrBuf[0] = '\0'; + } + + return [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding]; +} + ++ (uint16_t)portFromSockaddr4:(const struct sockaddr_in *)pSockaddr4 +{ + return ntohs(pSockaddr4->sin_port); +} + ++ (uint16_t)portFromSockaddr6:(const struct sockaddr_in6 *)pSockaddr6 +{ + return ntohs(pSockaddr6->sin6_port); +} + ++ (NSURL *)urlFromSockaddrUN:(const struct sockaddr_un *)pSockaddr +{ + NSString *path = [NSString stringWithUTF8String:pSockaddr->sun_path]; + return [NSURL fileURLWithPath:path]; +} + ++ (NSString *)hostFromAddress:(NSData *)address +{ + NSString *host; + + if ([self getHost:&host port:NULL fromAddress:address]) + return host; + else + return nil; +} + ++ (uint16_t)portFromAddress:(NSData *)address +{ + uint16_t port; + + if ([self getHost:NULL port:&port fromAddress:address]) + return port; + else + return 0; +} + ++ (BOOL)isIPv4Address:(NSData *)address +{ + if ([address length] >= sizeof(struct sockaddr)) + { + const struct sockaddr *sockaddrX = [address bytes]; + + if (sockaddrX->sa_family == AF_INET) { + return YES; + } + } + + return NO; +} + ++ (BOOL)isIPv6Address:(NSData *)address +{ + if ([address length] >= sizeof(struct sockaddr)) + { + const struct sockaddr *sockaddrX = [address bytes]; + + if (sockaddrX->sa_family == AF_INET6) { + return YES; + } + } + + return NO; +} + ++ (BOOL)getHost:(NSString **)hostPtr port:(uint16_t *)portPtr fromAddress:(NSData *)address +{ + return [self getHost:hostPtr port:portPtr family:NULL fromAddress:address]; +} + ++ (BOOL)getHost:(NSString **)hostPtr port:(uint16_t *)portPtr family:(sa_family_t *)afPtr fromAddress:(NSData *)address +{ + if ([address length] >= sizeof(struct sockaddr)) + { + const struct sockaddr *sockaddrX = [address bytes]; + + if (sockaddrX->sa_family == AF_INET) + { + if ([address length] >= sizeof(struct sockaddr_in)) + { + struct sockaddr_in sockaddr4; + memcpy(&sockaddr4, sockaddrX, sizeof(sockaddr4)); + + if (hostPtr) *hostPtr = [self hostFromSockaddr4:&sockaddr4]; + if (portPtr) *portPtr = [self portFromSockaddr4:&sockaddr4]; + if (afPtr) *afPtr = AF_INET; + + return YES; + } + } + else if (sockaddrX->sa_family == AF_INET6) + { + if ([address length] >= sizeof(struct sockaddr_in6)) + { + struct sockaddr_in6 sockaddr6; + memcpy(&sockaddr6, sockaddrX, sizeof(sockaddr6)); + + if (hostPtr) *hostPtr = [self hostFromSockaddr6:&sockaddr6]; + if (portPtr) *portPtr = [self portFromSockaddr6:&sockaddr6]; + if (afPtr) *afPtr = AF_INET6; + + return YES; + } + } + } + + return NO; +} + ++ (NSData *)CRLFData +{ + return [NSData dataWithBytes:"\x0D\x0A" length:2]; +} + ++ (NSData *)CRData +{ + return [NSData dataWithBytes:"\x0D" length:1]; +} + ++ (NSData *)LFData +{ + return [NSData dataWithBytes:"\x0A" length:1]; +} + ++ (NSData *)ZeroData +{ + return [NSData dataWithBytes:"" length:1]; +} + +@end diff --git a/actor-sdk/sdk-core-ios/Podfile b/actor-sdk/sdk-core-ios/Podfile index 16b7e2440b..c36114cfb4 100644 --- a/actor-sdk/sdk-core-ios/Podfile +++ b/actor-sdk/sdk-core-ios/Podfile @@ -12,7 +12,7 @@ target 'ActorApp' do # Core Tools pod 'RegexKitLite' - pod 'CocoaAsyncSocket' + # pod 'CocoaAsyncSocket' pod 'zipzap' # Main UI @@ -45,7 +45,7 @@ target 'ActorSDK' do # Core Tools pod 'RegexKitLite' - pod 'CocoaAsyncSocket' + # pod 'CocoaAsyncSocket' pod 'zipzap' # Main UI From 3d0fe364561e011eaf1a0a707d5fd9fbcfa82e88 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Wed, 20 Jul 2016 16:29:03 +0300 Subject: [PATCH 069/414] fix(core): Fixing iOS compatibility --- .../java/im/actor/core/i18n/I18nEngine.java | 4 ++-- .../modules/groups/router/GroupRouter.java | 20 +++++++++++-------- .../core/modules/users/router/UserRouter.java | 17 ++++++++++++++-- .../runtime/mvvm/ValueDoubleListener.java | 2 +- 4 files changed, 30 insertions(+), 13 deletions(-) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/i18n/I18nEngine.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/i18n/I18nEngine.java index 4d87232228..b13be8d3d8 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/i18n/I18nEngine.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/i18n/I18nEngine.java @@ -293,7 +293,7 @@ public String formatNotificationText(Notification pendingNotification) { * @param relatedUid optional related uid * @return formatted content */ - @ObjectiveCName("formatContentTextWithSenderId:withContentType:withText:withRelatedUid:withIsChannel") + @ObjectiveCName("formatContentTextWithSenderId:withContentType:withText:withRelatedUid:withIsChannel:") public String formatContentText(int senderId, ContentType contentType, String text, int relatedUid, boolean isChannel) { @@ -369,7 +369,7 @@ public String formatContentText(int senderId, ContentType contentType, String te * @param content content of a message * @return formatted message */ - @ObjectiveCName("formatFullServiceMessageWithSenderId:withContent:") + @ObjectiveCName("formatFullServiceMessageWithSenderId:withContent:withIsChannel:") public String formatFullServiceMessage(int senderId, ServiceContent content, boolean isChannel) { String groupKey = isChannel ? "channels" : "groups"; if (content instanceof ServiceUserRegistered) { diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/router/GroupRouter.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/router/GroupRouter.java index 923e7ff2d2..0d76dc6375 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/router/GroupRouter.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/router/GroupRouter.java @@ -288,14 +288,18 @@ private void onRequestLoadFullGroup(int gid) { freeze(); groups().getValueAsync(gid) - .flatMap(group -> { - if (!group.isHaveExtension()) { - ArrayList groups = new ArrayList<>(); - groups.add(new ApiGroupOutPeer(gid, group.getAccessHash())); - return api(new RequestLoadFullGroups(groups)) - .map(r -> group.updateExt(r.getGroups().get(0))); - } else { - return Promise.failure(new RuntimeException("Already loaded")); + // Do not reduce to lambda due j2objc bug + .flatMap(new Function>() { + @Override + public Promise apply(Group group) { + if (!group.isHaveExtension()) { + ArrayList groups = new ArrayList<>(); + groups.add(new ApiGroupOutPeer(gid, group.getAccessHash())); + return api(new RequestLoadFullGroups(groups)) + .map(r -> group.updateExt(r.getGroups().get(0))); + } else { + return Promise.failure(new RuntimeException("Already loaded")); + } } }) .then(r -> groups().addOrUpdateItem(r)) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/users/router/UserRouter.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/users/router/UserRouter.java index a3ded2dac2..fb25bd04af 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/users/router/UserRouter.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/users/router/UserRouter.java @@ -360,6 +360,7 @@ private void onLoadFullUser(int uid) { freeze(); users().getValueAsync(uid) + // Do not reduce to lambda due j2objc bug .flatMap((Function>>) u -> { if (!u.isHaveExtension()) { ArrayList users = new ArrayList<>(); @@ -386,7 +387,13 @@ private void onLoadFullUser(int uid) { private Promise> fetchMissingUsers(List users) { freeze(); return PromisesArray.of(users) - .map(u -> users().containsAsync(u.getUid()).map(v -> v ? null : u)) + // Do not reduce due j2objc bug + .map(new Function>() { + @Override + public Promise apply(ApiUserOutPeer u) { + return users().containsAsync(u.getUid()).map(v -> v ? null : u); + } + }) .filterNull() .zip() .after((r, e) -> unfreeze()); @@ -397,7 +404,13 @@ private Promise> fetchMissingUsers(List use private Promise applyUsers(List users) { freeze(); return PromisesArray.of(users) - .map(u -> users().containsAsync(u.getId()).map(v -> new Tuple2<>(u, v))) + // Do not reduce due j2objc bug + .map(new Function>>() { + @Override + public Promise> apply(ApiUser u) { + return users().containsAsync(u.getId()).map(v -> new Tuple2<>(u, v)); + } + }) .filter(t -> !t.getT2()) .zip() .then(x -> { diff --git a/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/mvvm/ValueDoubleListener.java b/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/mvvm/ValueDoubleListener.java index a706c10b1c..72c15e4098 100644 --- a/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/mvvm/ValueDoubleListener.java +++ b/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/mvvm/ValueDoubleListener.java @@ -4,6 +4,6 @@ public interface ValueDoubleListener { - @ObjectiveCName("onChanged:") + @ObjectiveCName("onChangedWithVal1:withVal2:") void onChanged(T1 val1, T2 val2); } From a60ce91152184004d0b39f13463c5b34d2b5c20a Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Wed, 20 Jul 2016 16:31:35 +0300 Subject: [PATCH 070/414] fix(core): Fixing iOS compatibility --- .../main/java/im/actor/core/modules/groups/GroupsModule.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsModule.java index 27fc3cac74..4e7fbb0e55 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsModule.java @@ -36,6 +36,7 @@ import im.actor.core.api.rpc.RequestTransferOwnership; import im.actor.core.api.rpc.ResponseIntegrationToken; import im.actor.core.api.rpc.ResponseInviteUrl; +import im.actor.core.api.rpc.ResponseVoid; import im.actor.core.entity.Group; import im.actor.core.entity.GroupMembersSlice; import im.actor.core.entity.GroupPermissions; @@ -67,6 +68,10 @@ public class GroupsModule extends AbsModule implements BusSubscriber { + // Workaround for j2objc bug + private static final Void DUMB = null; + private static final ResponseVoid DUMB2 = null; + private final KeyValueEngine groups; private final MVVMCollection collection; private final HashMap avatarVMs; From a8e5dd81f012fb53ef9b07af930039b9a14e40a6 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Wed, 20 Jul 2016 17:24:43 +0300 Subject: [PATCH 071/414] fix(android): share location coordinates --- .../sdk/controllers/conversation/attach/AttachFragment.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/attach/AttachFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/attach/AttachFragment.java index 7adda58b83..151bba9898 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/attach/AttachFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/attach/AttachFragment.java @@ -349,8 +349,8 @@ public void onContactPicked(String name, List phones, List email } @Override - public void onLocationPicked(double latitude, double longitude, String street, String place) { - messenger().sendLocation(getPeer(), longitude, longitude, street, place); + public void onLocationPicked(double longitude, double latitude, String street, String place) { + messenger().sendLocation(getPeer(), longitude, latitude, street, place); } @Override From 3fbee79866e0c134be4df4e23cafc3fe72798677 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Wed, 20 Jul 2016 17:29:38 +0300 Subject: [PATCH 072/414] feat(iOS): Mute/unmute channels, channel support for bubbles, --- .../ActorSDK/Sources/ActorSDK.swift | 2 +- .../Compose/AAGroupMembersController.swift | 24 +++--- .../Conversation/Cell/AABubbleCell.swift | 40 +++++----- .../Cell/AABubbleServiceCell.swift | 10 ++- .../Views/AAVoiceRecorderView.swift | 2 +- .../ConversationViewController.swift | 70 +++++++++++++++-- .../AAAddParticipantViewController.swift | 2 +- .../Group/AAGroupViewController.swift | 78 +++++++++---------- .../Group/AAInviteLinkViewController.swift | 4 +- 9 files changed, 148 insertions(+), 84 deletions(-) diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorSDK.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorSDK.swift index 0c7bd11c27..21f8fb0ecb 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorSDK.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorSDK.swift @@ -647,7 +647,7 @@ import DZNWebViewController let alert = UIAlertController(title: nil, message: AALocalized("GroupJoinMessage"), preferredStyle: .Alert) alert.addAction(UIAlertAction(title: AALocalized("AlertNo"), style: .Cancel, handler: nil)) alert.addAction(UIAlertAction(title: AALocalized("GroupJoinAction"), style: .Default){ (action) -> Void in - AAExecutions.execute(Actor.joinGroupViaLinkCommandWithToken(token)!, type: .Safe, ignore: [], successBlock: { (val) -> Void in + AAExecutions.execute(Actor.joinGroupViaLinkCommandWithToken(token), type: .Safe, ignore: [], successBlock: { (val) -> Void in // TODO: Fix for iPad let groupId = val as! JavaLangInteger diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Compose/AAGroupMembersController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Compose/AAGroupMembersController.swift index 888ced2978..3c337f6b79 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Compose/AAGroupMembersController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Compose/AAGroupMembersController.swift @@ -81,18 +81,18 @@ public class GroupMembersController: AAContactsListContentController, AAContacts res.replaceIntAtIndex(UInt(i), withInt: selected[i].contact.uid) } - executeSafeOnlySuccess(Actor.createGroupCommandWithTitle(groupTitle, withAvatar: nil, withUids: res)!) { (val) -> Void in - let gid = (val as! JavaLangInteger).intValue - if self.groupImage != nil { - Actor.changeGroupAvatar(gid, image: self.groupImage!) - } - if let customController = ActorSDK.sharedActor().delegate.actorControllerForConversation(ACPeer.groupWithInt(gid)) { - self.navigateDetail(customController) - } else { - self.navigateDetail(ConversationViewController(peer: ACPeer.groupWithInt(gid))) - } - self.dismiss() - } +// executeSafeOnlySuccess(Actor.createGroupWithTitle(groupTitle, withAvatar: nil, withUids: res)) { (val) -> Void in +// let gid = (val as! JavaLangInteger).intValue +// if self.groupImage != nil { +// Actor.changeGroupAvatar(gid, image: self.groupImage!) +// } +// if let customController = ActorSDK.sharedActor().delegate.actorControllerForConversation(ACPeer.groupWithInt(gid)) { +// self.navigateDetail(customController) +// } else { +// self.navigateDetail(ConversationViewController(peer: ACPeer.groupWithInt(gid))) +// } +// self.dismiss() +// } } // Handling token input updates diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Cell/AABubbleCell.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Cell/AABubbleCell.swift index 650c98c357..294eed0f81 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Cell/AABubbleCell.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Cell/AABubbleCell.swift @@ -365,60 +365,60 @@ public class AABubbleCell: UICollectionViewCell { case BubbleType.TextIn: if (!isFullSize!) { bubbleShadow.image = AABubbleCell.cachedInTextCompactBgShadow - bubbleShadow.highlightedImage = AABubbleCell.cachedInTextCompactBgShadow + // bubbleShadow.highlightedImage = AABubbleCell.cachedInTextCompactBgShadow bubble.image = AABubbleCell.cachedInTextCompactBg bubbleBorder.image = AABubbleCell.cachedInTextCompactBgBorder - bubble.highlightedImage = AABubbleCell.cachedInTextCompactSelectedBg - bubbleBorder.highlightedImage = AABubbleCell.cachedInTextCompactBgBorder + // bubble.highlightedImage = AABubbleCell.cachedInTextCompactSelectedBg + // bubbleBorder.highlightedImage = AABubbleCell.cachedInTextCompactBgBorder } else { bubbleShadow.image = AABubbleCell.cachedInTextBgShadow - bubbleShadow.highlightedImage = AABubbleCell.cachedInTextBgShadow + // bubbleShadow.highlightedImage = AABubbleCell.cachedInTextBgShadow bubble.image = AABubbleCell.cachedInTextBg bubbleBorder.image = AABubbleCell.cachedInTextBgBorder - bubble.highlightedImage = AABubbleCell.cachedInTextBg - bubbleBorder.highlightedImage = AABubbleCell.cachedInTextBgBorder + // bubble.highlightedImage = AABubbleCell.cachedInTextBg + // bubbleBorder.highlightedImage = AABubbleCell.cachedInTextBgBorder } break case BubbleType.TextOut: if (!isFullSize!) { bubbleShadow.image = AABubbleCell.cachedOutTextCompactBgShadow - bubbleShadow.highlightedImage = AABubbleCell.cachedOutTextCompactBgShadow + // bubbleShadow.highlightedImage = AABubbleCell.cachedOutTextCompactBgShadow bubble.image = AABubbleCell.cachedOutTextCompactBg bubbleBorder.image = AABubbleCell.cachedOutTextCompactBgBorder - bubble.highlightedImage = AABubbleCell.cachedOutTextCompactSelectedBg - bubbleBorder.highlightedImage = AABubbleCell.cachedOutTextCompactBgBorder + // bubble.highlightedImage = AABubbleCell.cachedOutTextCompactSelectedBg + // bubbleBorder.highlightedImage = AABubbleCell.cachedOutTextCompactBgBorder } else { bubbleShadow.image = AABubbleCell.cachedOutTextBgShadow - bubbleShadow.highlightedImage = AABubbleCell.cachedOutTextBgShadow + // bubbleShadow.highlightedImage = AABubbleCell.cachedOutTextBgShadow bubble.image = AABubbleCell.cachedOutTextBg bubbleBorder.image = AABubbleCell.cachedOutTextBgBorder - bubble.highlightedImage = AABubbleCell.cachedOutTextBg - bubbleBorder.highlightedImage = AABubbleCell.cachedOutTextBgBorder + // bubble.highlightedImage = AABubbleCell.cachedOutTextBg + // bubbleBorder.highlightedImage = AABubbleCell.cachedOutTextBgBorder } break case BubbleType.MediaIn: bubble.image = AABubbleCell.cachedMediaBg bubbleBorder.image = AABubbleCell.cachedMediaBgBorder - bubble.highlightedImage = AABubbleCell.cachedMediaBg - bubbleBorder.highlightedImage = AABubbleCell.cachedMediaBgBorder + // bubble.highlightedImage = AABubbleCell.cachedMediaBg + // bubbleBorder.highlightedImage = AABubbleCell.cachedMediaBgBorder break case BubbleType.MediaOut: bubble.image = AABubbleCell.cachedMediaBg bubbleBorder.image = AABubbleCell.cachedMediaBgBorder - bubble.highlightedImage = AABubbleCell.cachedMediaBg - bubbleBorder.highlightedImage = AABubbleCell.cachedMediaBgBorder + // bubble.highlightedImage = AABubbleCell.cachedMediaBg + // bubbleBorder.highlightedImage = AABubbleCell.cachedMediaBgBorder break case BubbleType.Service: bubble.image = AABubbleCell.cachedServiceBg bubbleBorder.image = nil - bubble.highlightedImage = AABubbleCell.cachedServiceBg - bubbleBorder.highlightedImage = nil + // bubble.highlightedImage = AABubbleCell.cachedServiceBg + // bubbleBorder.highlightedImage = nil break case BubbleType.Sticker: bubble.image = nil; bubbleBorder.image = nil - bubble.highlightedImage = nil; - bubbleBorder.highlightedImage = nil + // bubble.highlightedImage = nil; + // bubbleBorder.highlightedImage = nil break } } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Cell/AABubbleServiceCell.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Cell/AABubbleServiceCell.swift index e79bec0b6c..4cc732d057 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Cell/AABubbleServiceCell.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Cell/AABubbleServiceCell.swift @@ -83,7 +83,15 @@ public class AABubbleServiceCellLayouter: AABubbleLayouter { } public func buildLayout(peer: ACPeer, message: ACMessage) -> AACellLayout { - let serviceText = Actor.getFormatter().formatFullServiceMessageWithSenderId(message.senderId, withContent: message.content as! ACServiceContent) + + let isChannel: Bool + if peer.isGroup { + isChannel = Actor.getGroupWithGid(peer.peerId).groupType == ACGroupType.CHANNEL() + } else { + isChannel = false + } + + let serviceText = Actor.getFormatter().formatFullServiceMessageWithSenderId(message.senderId, withContent: message.content as! ACServiceContent, withIsChannel: isChannel) return ServiceCellLayout(text: serviceText, date: Int64(message.date), layouter: self) } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Views/AAVoiceRecorderView.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Views/AAVoiceRecorderView.swift index 663cb3a326..c0d1954eac 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Views/AAVoiceRecorderView.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Views/AAVoiceRecorderView.swift @@ -138,7 +138,7 @@ class AAVoiceRecorderView: UIView { self.sliderArrow.frame = CGRectMake(310,12,20,20) self.recorderImageCircle.frame = CGRectMake(-110, 15, 14, 14) - UIView.animateWithDuration(1.5, delay: 0.0, usingSpringWithDamping: 0.5, initialSpringVelocity: 1.0, options: UIViewAnimationOptions.CurveLinear, animations: { () -> Void in + UIView.animateWithDuration(0.5, delay: 0.0, usingSpringWithDamping: 0.5, initialSpringVelocity: 1.0, options: UIViewAnimationOptions.CurveLinear, animations: { () -> Void in self.timeLabel.frame = CGRectMake(29, 12, 50, 20) self.sliderLabel.frame = CGRectMake(140,12,100,20) diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Conversation/ConversationViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Conversation/ConversationViewController.swift index a5d0f80c60..2db42f9b86 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Conversation/ConversationViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Conversation/ConversationViewController.swift @@ -41,7 +41,8 @@ public class ConversationViewController: private let backgroundView = UIImageView() private var audioButton: UIButton = UIButton() private var voiceRecorderView : AAVoiceRecorderView! - + private let inputOverlay = UIView() + private let inputOverlayLabel = UILabel() // // Stickers @@ -126,6 +127,17 @@ public class ConversationViewController: self.textView.keyboardAppearance = ActorSDK.sharedActor().style.isDarkApp ? .Dark : .Light + // + // Overlay + // + self.inputOverlay.addSubview(inputOverlayLabel) + self.inputOverlayLabel.textAlignment = .Center + self.inputOverlayLabel.font = UIFont.systemFontOfSize(18) + self.inputOverlayLabel.textColor = ActorSDK.sharedActor().style.vcTintColor + self.inputOverlay.viewDidTap = { + self.onOverlayTap() + } + // // Add stickers button // @@ -249,11 +261,15 @@ public class ConversationViewController: override public func viewDidLoad() { super.viewDidLoad() - self.voiceRecorderView = AAVoiceRecorderView(frame: CGRectMake(0,0,self.view.frame.size.width-30,44)) + self.voiceRecorderView = AAVoiceRecorderView(frame: CGRectMake(0, 0, view.width - 30, 44)) self.voiceRecorderView.hidden = true self.voiceRecorderView.binedController = self self.textInputbar.addSubview(self.voiceRecorderView) + self.inputOverlay.backgroundColor = UIColor.whiteColor() + self.inputOverlay.hidden = false + self.textInputbar.addSubview(self.inputOverlay) + navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: UIBarButtonItemStyle.Plain, target: nil, action: nil) let frame = CGRectMake(0, 0, self.view.frame.size.width, 216) @@ -271,6 +287,9 @@ public class ConversationViewController: super.viewDidLayoutSubviews() self.stickersButton.frame = CGRectMake(self.view.frame.size.width-67, 12, 20, 20) + self.voiceRecorderView.frame = CGRectMake(0, 0, view.width - 30, 44) + self.inputOverlay.frame = CGRectMake(0, 0, view.width, 44) + self.inputOverlayLabel.frame = CGRectMake(0, 0, view.width, 44) } //////////////////////////////////////////////////////////// @@ -327,14 +346,11 @@ public class ConversationViewController: binder.bind(group.getAvatarModel(), closure: { (value: ACAvatar?) -> () in self.avatarView.bind(group.getNameModel().get(), id: Int(group.getId()), avatar: value) }) - binder.bind(Actor.getGroupTypingWithGid(group.getId()), valueModel2: group.getMembersModel(), valueModel3: group.getPresenceModel(), closure: { (typingValue:IOSIntArray?, members:JavaUtilHashSet?, onlineCount:JavaLangInteger?) -> () in + binder.bind(Actor.getGroupTypingWithGid(group.getId()), valueModel2: group.membersCount, valueModel3: group.getPresenceModel(), closure: { (typingValue:IOSIntArray?, membersCount: JavaLangInteger?, onlineCount:JavaLangInteger?) -> () in if (!group.isMemberModel().get().booleanValue()) { self.subtitleView.text = AALocalized("ChatNoGroupAccess") self.subtitleView.textColor = self.appStyle.navigationSubtitleColor - self.setTextInputbarHidden(true, animated: true) return - } else { - self.setTextInputbarHidden(false, animated: false) } if (typingValue != nil && typingValue!.length() > 0) { @@ -347,7 +363,7 @@ public class ConversationViewController: self.subtitleView.text = Actor.getFormatter().formatTypingWithCount(typingValue!.length()); } } else { - var membersString = Actor.getFormatter().formatGroupMembers(members!.size()) + var membersString = Actor.getFormatter().formatGroupMembers(membersCount!.intValue()) self.subtitleView.textColor = self.appStyle.navigationSubtitleColor if (onlineCount == nil || onlineCount!.integerValue == 0) { self.subtitleView.text = membersString; @@ -360,6 +376,27 @@ public class ConversationViewController: } } }) + + binder.bind(group.isMember, valueModel2: group.isCanWriteMessage) { (isMember: JavaLangBoolean!, canWriteMessage: JavaLangBoolean!) in + if canWriteMessage.booleanValue() { + self.stickersButton.hidden = false + self.inputOverlay.hidden = true + } else { + if !isMember.booleanValue() { + self.inputOverlayLabel.text = "Not a member" + } else { + if Actor.isNotificationsEnabledWithPeer(self.peer) { + self.inputOverlayLabel.text = "Mute" + } else { + self.inputOverlayLabel.text = "Unmute" + } + } + self.stickersButton.hidden = true + self.stopAudioRecording() + self.textInputbar.textView.text = "" + self.inputOverlay.hidden = false + } + } } Actor.onConversationOpenWithPeer(peer) @@ -374,6 +411,25 @@ public class ConversationViewController: } + public func onOverlayTap() { + if peer.isGroup { + let group = Actor.getGroupWithGid(peer.peerId) + if !group.isMember.get().booleanValue() { + // DO NOTHING + } else if !group.isCanWriteMessage.get().booleanValue() { + if Actor.isNotificationsEnabledWithPeer(peer) { + Actor.changeNotificationsEnabledWithPeer(peer, withValue: false) + inputOverlayLabel.text = "Unmute" + } else { + Actor.changeNotificationsEnabledWithPeer(peer, withValue: true) + inputOverlayLabel.text = "Mute" + } + } + } else if peer.isPrivate { + + } + } + override public func viewWillLayoutSubviews() { super.viewWillLayoutSubviews() diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAAddParticipantViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAAddParticipantViewController.swift index 30a427ef45..154f045414 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAAddParticipantViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAAddParticipantViewController.swift @@ -48,7 +48,7 @@ public class AAAddParticipantViewController: AAContactsListContentController, AA public func contactDidTap(controller: AAContactsListContentController, contact: ACContact) -> Bool { if !isAlreadyMember(contact.uid) { - self.executeSafeOnlySuccess(Actor.inviteMemberCommandWithGid(jint(gid), withUid: jint(contact.uid))!) { (val) -> () in + self.executeSafeOnlySuccess(Actor.inviteMemberCommandWithGid(jint(gid), withUid: jint(contact.uid))) { (val) -> () in self.dismiss() } } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewController.swift index a3fa1c7b5a..0e4358fe72 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewController.swift @@ -105,9 +105,9 @@ public class AAGroupViewController: AAContentTableController { return } - c.executeSafeOnlySuccess(Actor.editGroupTitleCommandWithGid(jint(self.gid), withTitle: t)!, successBlock: { (val) -> Void in - c.dismiss() - }) +// c.executeSafeOnlySuccess(Actor.editGroupTitleCommandWithGid(jint(self.gid), withTitle: t)!, successBlock: { (val) -> Void in +// c.dismiss() +// }) } } @@ -274,42 +274,42 @@ public class AAGroupViewController: AAContentTableController { } }) - // Detect if we are admin - let members: [ACGroupMember] = self.group.members.get().toArray().toSwiftArray() - var isAdmin = self.group.creatorId == Actor.myUid() - if !isAdmin { - for m in members { - if m.uid == Actor.myUid() { - isAdmin = m.isAdministrator - } - } - } - - // Can mark as admin - let canMarkAdmin = isAdmin && !d.isAdministrator - - if canMarkAdmin { - a.action("GroupMemberMakeAdmin") { () -> () in - - self.confirmDestructive(AALocalized("GroupMemberMakeMessage").replace("{name}", dest: name), action: AALocalized("GroupMemberMakeAction")) { - - self.executeSafe(Actor.makeAdminCommandWithGid(jint(self.gid), withUid: jint(user.getId()))!) - } - } - } - +// // Detect if we are admin +// let members: [ACGroupMember] = self.group.members.get().toArray().toSwiftArray() +// var isAdmin = self.group.creatorId == Actor.myUid() +// if !isAdmin { +// for m in members { +// if m.uid == Actor.myUid() { +// isAdmin = m.isAdministrator +// } +// } +// } +// +// // Can mark as admin +// let canMarkAdmin = isAdmin && !d.isAdministrator +// +// if canMarkAdmin { +// a.action("GroupMemberMakeAdmin") { () -> () in +// +// self.confirmDestructive(AALocalized("GroupMemberMakeMessage").replace("{name}", dest: name), action: AALocalized("GroupMemberMakeAction")) { +// +// self.executeSafe(Actor.makeAdminCommandWithGid(jint(self.gid), withUid: jint(user.getId()))!) +// } +// } +// } + // Can kick user - let canKick = isAdmin || d.inviterUid == Actor.myUid() - - if canKick { - a.destructive("GroupMemberKick") { () -> () in - self.confirmDestructive(AALocalized("GroupMemberKickMessage") - .replace("{name}", dest: name), action: AALocalized("GroupMemberKickAction")) { - - self.executeSafe(Actor.kickMemberCommandWithGid(jint(self.gid), withUid: user.getId())!) - } - } - } +// let canKick = isAdmin || d.inviterUid == Actor.myUid() +// +// if canKick { +// a.destructive("GroupMemberKick") { () -> () in +// self.confirmDestructive(AALocalized("GroupMemberKickMessage") +// .replace("{name}", dest: name), action: AALocalized("GroupMemberKickAction")) { +// +// self.executeSafe(Actor.kickMemberCommandWithGid(jint(self.gid), withUid: user.getId())!) +// } +// } +// } } return true @@ -325,7 +325,7 @@ public class AAGroupViewController: AAContentTableController { r.selectAction = { () -> Bool in self.confirmDestructive(AALocalized("GroupLeaveConfirm"), action: AALocalized("GroupLeaveConfirmAction"), yes: { () -> () in - self.executeSafe(Actor.leaveGroupCommandWithGid(jint(self.gid))!) + // self.executeSafe(Actor.leaveGroupCommandWithGid(jint(self.gid))!) }) return true diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAInviteLinkViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAInviteLinkViewController.swift index 89bb38a0bf..7af9ab4a08 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAInviteLinkViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAInviteLinkViewController.swift @@ -71,7 +71,7 @@ public class AAInviteLinkViewController: AAContentTableController { } } - executeSafe(Actor.requestInviteLinkCommandWithGid(jint(gid))!) { (val) -> Void in + executeSafe(Actor.requestInviteLinkCommandWithGid(jint(gid))) { (val) -> Void in self.currentUrl = val as? String self.urlRow.reload() self.tableView.hidden = false @@ -79,7 +79,7 @@ public class AAInviteLinkViewController: AAContentTableController { } public func reloadLink() { - executeSafe(Actor.requestRevokeLinkCommandWithGid(jint(gid))!) { (val) -> Void in + executeSafe(Actor.requestRevokeLinkCommandWithGid(jint(gid))) { (val) -> Void in self.currentUrl = val as? String self.urlRow.reload() self.tableView.hidden = false From a54b2cd34b4eeef6c19307e260dccc27d1f9eae0 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Wed, 20 Jul 2016 22:21:43 +0300 Subject: [PATCH 073/414] feat(iOS): Respect permissions on group info edit --- .../ActorSDK.xcodeproj/project.pbxproj | 4 + .../Group/AAGroupEditInfoViewController.swift | 23 +++ .../Group/AAGroupViewController.swift | 147 +++++++++--------- .../java/im/actor/core/i18n/I18nEngine.java | 2 +- 4 files changed, 105 insertions(+), 71 deletions(-) create mode 100644 actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupEditInfoViewController.swift diff --git a/actor-sdk/sdk-core-ios/ActorSDK.xcodeproj/project.pbxproj b/actor-sdk/sdk-core-ios/ActorSDK.xcodeproj/project.pbxproj index 1b08a8b613..88db792410 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK.xcodeproj/project.pbxproj +++ b/actor-sdk/sdk-core-ios/ActorSDK.xcodeproj/project.pbxproj @@ -235,6 +235,7 @@ 069CF4D31BCB909A00C66E12 /* CLTokenView.m in Sources */ = {isa = PBXBuildFile; fileRef = 069CF4CB1BCB909A00C66E12 /* CLTokenView.m */; }; 06ABFE331D3FAF800031A0D6 /* GCDAsyncSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 06ABFE2F1D3FAF800031A0D6 /* GCDAsyncSocket.h */; settings = {ATTRIBUTES = (Public, ); }; }; 06ABFE341D3FAF800031A0D6 /* GCDAsyncSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 06ABFE301D3FAF800031A0D6 /* GCDAsyncSocket.m */; }; + 06ABFE381D3FCCE30031A0D6 /* AAGroupEditInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06ABFE371D3FCCE30031A0D6 /* AAGroupEditInfoViewController.swift */; }; 06B489ED1C9F6EBD0054245B /* AAStickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06B489EC1C9F6EBC0054245B /* AAStickerView.swift */; }; 06C1D0771C8BC9FC00B73632 /* AAAuthNameViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06C1D0761C8BC9FC00B73632 /* AAAuthNameViewController.swift */; }; 06C1D07B1C8BFE5C00B73632 /* AAAuthPhoneViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06C1D07A1C8BFE5C00B73632 /* AAAuthPhoneViewController.swift */; }; @@ -609,6 +610,7 @@ 069CF4CB1BCB909A00C66E12 /* CLTokenView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CLTokenView.m; sourceTree = ""; }; 06ABFE2F1D3FAF800031A0D6 /* GCDAsyncSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDAsyncSocket.h; sourceTree = ""; }; 06ABFE301D3FAF800031A0D6 /* GCDAsyncSocket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDAsyncSocket.m; sourceTree = ""; }; + 06ABFE371D3FCCE30031A0D6 /* AAGroupEditInfoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AAGroupEditInfoViewController.swift; sourceTree = ""; }; 06B489EC1C9F6EBC0054245B /* AAStickerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AAStickerView.swift; sourceTree = ""; }; 06C1D0761C8BC9FC00B73632 /* AAAuthNameViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AAAuthNameViewController.swift; sourceTree = ""; }; 06C1D07A1C8BFE5C00B73632 /* AAAuthPhoneViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AAAuthPhoneViewController.swift; sourceTree = ""; }; @@ -1235,6 +1237,7 @@ 066A52E31BC52A20000E606E /* AAAddParticipantViewController.swift */, 066A52E21BC52A20000E606E /* AAGroupViewController.swift */, 066A52E41BC52A20000E606E /* AAInviteLinkViewController.swift */, + 06ABFE371D3FCCE30031A0D6 /* AAGroupEditInfoViewController.swift */, ); path = Group; sourceTree = ""; @@ -2276,6 +2279,7 @@ 066A52231BC4EEAC000E606E /* AAManagedSection.swift in Sources */, 066A52D11BC52204000E606E /* AADialogCell.swift in Sources */, 066A51901BC4C383000E606E /* CocoaNetworkRuntime.swift in Sources */, + 06ABFE381D3FCCE30031A0D6 /* AAGroupEditInfoViewController.swift in Sources */, 06E7B24A1C0F92140090660C /* AABubbleLocationCell.swift in Sources */, 066A52581BC4EF61000E606E /* Alerts.swift in Sources */, 066A51691BC4C366000E606E /* AATools.swift in Sources */, diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupEditInfoViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupEditInfoViewController.swift new file mode 100644 index 0000000000..6a8dfc524d --- /dev/null +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupEditInfoViewController.swift @@ -0,0 +1,23 @@ +// +// Copyright (c) 2014-2016 Actor LLC. +// + +import Foundation + +public class AAGroupEditInfoController: AAViewController { + + public init(gid: Int) { + super.init() + self.gid = gid + } + + public required init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public override func viewDidLoad() { + super.viewDidLoad() + + + } +} \ No newline at end of file diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewController.swift index 0e4358fe72..82659eb203 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewController.swift @@ -58,60 +58,63 @@ public class AAGroupViewController: AAContentTableController { } } - // Header: Change photo - s.action("GroupSetPhoto") { (r) -> () in - r.selectAction = { () -> Bool in - let hasCamera = UIImagePickerController.isSourceTypeAvailable(UIImagePickerControllerSourceType.Camera) - self.showActionSheet( hasCamera ? ["PhotoCamera", "PhotoLibrary"] : ["PhotoLibrary"], - cancelButton: "AlertCancel", - destructButton: self.group.getAvatarModel().get() != nil ? "PhotoRemove" : nil, - sourceView: self.view, - sourceRect: self.view.bounds, - tapClosure: { (index) -> () in - if (index == -2) { - self.confirmAlertUser("PhotoRemoveGroupMessage", - action: "PhotoRemove", - tapYes: { () -> () in - Actor.removeGroupAvatarWithGid(jint(self.gid)) - }, tapNo: nil) - } else if (index >= 0) { - let takePhoto: Bool = (index == 0) && hasCamera - self.pickAvatar(takePhoto, closure: { (image) -> () in - Actor.changeGroupAvatar(jint(self.gid), image: image) - }) - } - }) - - return true - } - } - - // Header: Change title - s.action("GroupSetTitle") { (r) -> () in - r.selectAction = { () -> Bool in - self.startEditField { (c) -> () in - - c.title = "GroupEditHeader" - - c.fieldHint = "GroupEditHint" - - c.actionTitle = "NavigationSave" - - c.initialText = self.group.getNameModel().get() + if self.group.isCanEditInfo.get().booleanValue() { + + // Header: Change photo + s.action("GroupSetPhoto") { (r) -> () in + r.selectAction = { () -> Bool in + let hasCamera = UIImagePickerController.isSourceTypeAvailable(UIImagePickerControllerSourceType.Camera) + self.showActionSheet( hasCamera ? ["PhotoCamera", "PhotoLibrary"] : ["PhotoLibrary"], + cancelButton: "AlertCancel", + destructButton: self.group.getAvatarModel().get() != nil ? "PhotoRemove" : nil, + sourceView: self.view, + sourceRect: self.view.bounds, + tapClosure: { (index) -> () in + if (index == -2) { + self.confirmAlertUser("PhotoRemoveGroupMessage", + action: "PhotoRemove", + tapYes: { () -> () in + Actor.removeGroupAvatarWithGid(jint(self.gid)) + }, tapNo: nil) + } else if (index >= 0) { + let takePhoto: Bool = (index == 0) && hasCamera + self.pickAvatar(takePhoto, closure: { (image) -> () in + Actor.changeGroupAvatar(jint(self.gid), image: image) + }) + } + }) - c.didDoneTap = { (t, c) -> () in + return true + } + } + + // Header: Change title + s.action("GroupSetTitle") { (r) -> () in + r.selectAction = { () -> Bool in + self.startEditField { (c) -> () in - if t.length == 0 { - return - } + c.title = "GroupEditHeader" + + c.fieldHint = "GroupEditHint" -// c.executeSafeOnlySuccess(Actor.editGroupTitleCommandWithGid(jint(self.gid), withTitle: t)!, successBlock: { (val) -> Void in -// c.dismiss() -// }) + c.actionTitle = "NavigationSave" + + c.initialText = self.group.getNameModel().get() + + c.didDoneTap = { (t, c) -> () in + + if t.length == 0 { + return + } + + // c.executeSafeOnlySuccess(Actor.editGroupTitleCommandWithGid(jint(self.gid), withTitle: t)!, successBlock: { (val) -> Void in + // c.dismiss() + // }) + } } + + return true } - - return true } } @@ -121,11 +124,13 @@ public class AAGroupViewController: AAContentTableController { if (ActorSDK.sharedActor().enableCalls) { let members = (group.members.get() as! JavaUtilHashSet).size() if (members <= 20) { // Temporary limitation - section { (s) -> () in - s.action("CallsStartGroupAudio") { (r) -> () in - r.selectAction = { () -> Bool in - self.execute(Actor.doCallWithGid(jint(self.gid))) - return true + if group.groupType == ACGroupType.GROUP() { + section { (s) -> () in + s.action("CallsStartGroupAudio") { (r) -> () in + r.selectAction = { () -> Bool in + self.execute(Actor.doCallWithGid(jint(self.gid))) + return true + } } } } @@ -176,6 +181,24 @@ public class AAGroupViewController: AAContentTableController { } } + if group.isCanLeave.get().booleanValue() { + // Leave group + section { (s) -> () in + s.common({ (r) -> () in + r.content = AALocalized("GroupLeave") + r.style = .Destructive + r.selectAction = { () -> Bool in + + self.confirmDestructive(AALocalized("GroupLeaveConfirm"), action: AALocalized("GroupLeaveConfirmAction"), yes: { () -> () in + // self.executeSafe(Actor.leaveGroupCommandWithGid(jint(self.gid))!) + }) + + return true + } + }) + } + } + // Members section { (s) -> () in @@ -316,22 +339,6 @@ public class AAGroupViewController: AAContentTableController { } } } - - // Leave group - section { (s) -> () in - s.common({ (r) -> () in - r.content = AALocalized("GroupLeave") - r.style = .DestructiveCentered - r.selectAction = { () -> Bool in - - self.confirmDestructive(AALocalized("GroupLeaveConfirm"), action: AALocalized("GroupLeaveConfirmAction"), yes: { () -> () in - // self.executeSafe(Actor.leaveGroupCommandWithGid(jint(self.gid))!) - }) - - return true - } - }) - } } public override func tableWillBind(binder: AABinder) { diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/i18n/I18nEngine.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/i18n/I18nEngine.java index b13be8d3d8..1b4831bafe 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/i18n/I18nEngine.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/i18n/I18nEngine.java @@ -227,7 +227,7 @@ public String formatDialogText(Dialog dialog) { String contentText = formatContentText(dialog.getSenderId(), dialog.getMessageType(), dialog.getText(), dialog.getRelatedUid(), dialog.isChannel()); - if (dialog.getPeer().getPeerType() == PeerType.GROUP) { + if (dialog.getPeer().getPeerType() == PeerType.GROUP && !dialog.isChannel()) { if (!isLargeDialogMessage(dialog.getMessageType())) { return formatPerformerName(dialog.getSenderId()) + ": " + contentText; } else { From 5858658d6d1d506fa2964e9404e63c47b54f3feb Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Wed, 20 Jul 2016 22:58:47 +0300 Subject: [PATCH 074/414] feat(scheme): IsDeleted group state, updated peer search api --- actor-sdk/sdk-api/actor.json | 122 ++++++------------ .../models/im/actor/api/scheme.mps | 104 +++++---------- 2 files changed, 77 insertions(+), 149 deletions(-) diff --git a/actor-sdk/sdk-api/actor.json b/actor-sdk/sdk-api/actor.json index 89dfe32e9f..36aed64ab7 100644 --- a/actor-sdk/sdk-api/actor.json +++ b/actor-sdk/sdk-api/actor.json @@ -7936,6 +7936,12 @@ "category": "full", "description": " Can user send messages. Default is equals isMember for Group and false for others." }, + { + "type": "reference", + "argument": "isDeleted", + "category": "full", + "description": " Is this group deleted" + }, { "type": "reference", "argument": "ext", @@ -8053,6 +8059,14 @@ "id": 26, "name": "canSendMessage" }, + { + "type": { + "type": "opt", + "childType": "bool" + }, + "id": 27, + "name": "isDeleted" + }, { "type": { "type": "opt", @@ -9326,6 +9340,32 @@ ] } }, + { + "type": "update", + "content": { + "name": "GroupDeleted", + "header": 2658, + "doc": [ + "Update about group deleted", + { + "type": "reference", + "argument": "groupId", + "category": "full", + "description": " Group Id" + } + ], + "attributes": [ + { + "type": { + "type": "alias", + "childType": "groupId" + }, + "id": 1, + "name": "groupId" + } + ] + } + }, { "type": "comment", "content": " " @@ -12699,39 +12739,9 @@ }, { "type": "reference", - "argument": "title", - "category": "full", - "description": " Peer title" - }, - { - "type": "reference", - "argument": "description", + "argument": "optMatchString", "category": "full", "description": " Description" - }, - { - "type": "reference", - "argument": "membersCount", - "category": "full", - "description": " Members count" - }, - { - "type": "reference", - "argument": "dateCreated", - "category": "full", - "description": " Group Creation Date" - }, - { - "type": "reference", - "argument": "creator", - "category": "full", - "description": " Group Creator uid" - }, - { - "type": "reference", - "argument": "isPublic", - "category": "full", - "description": " Is group public" } ], "attributes": [ @@ -12743,61 +12753,13 @@ "id": 1, "name": "peer" }, - { - "type": "string", - "id": 2, - "name": "title" - }, { "type": { "type": "opt", "childType": "string" }, "id": 3, - "name": "description" - }, - { - "type": { - "type": "opt", - "childType": "int32" - }, - "id": 4, - "name": "membersCount" - }, - { - "type": { - "type": "opt", - "childType": { - "type": "alias", - "childType": "date" - } - }, - "id": 5, - "name": "dateCreated" - }, - { - "type": { - "type": "opt", - "childType": "int32" - }, - "id": 6, - "name": "creator" - }, - { - "type": { - "type": "opt", - "childType": "bool" - }, - "id": 7, - "name": "isPublic" - }, - { - "type": { - "type": "opt", - "childType": "bool" - }, - "id": 8, - "name": "isJoined" + "name": "optMatchString" } ] } diff --git a/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps b/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps index 0e440a746b..d447bbbd88 100644 --- a/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps +++ b/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps @@ -6972,6 +6972,11 @@ + + + + + @@ -7068,6 +7073,13 @@ + + + + + + + @@ -8122,6 +8134,27 @@ + + + + + + + + + + + + + + + + + + + + + @@ -10952,55 +10985,13 @@ - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -11009,35 +11000,10 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - + From 30f5a0a96ead3f9e2119b6e998ec81d7e591896a Mon Sep 17 00:00:00 2001 From: kor_ka Date: Thu, 21 Jul 2016 01:55:07 +0300 Subject: [PATCH 075/414] fix(android): forward text crash fix --- .../controllers/conversation/ChatActivity.java | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatActivity.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatActivity.java index ef30244fb0..f740e82fcc 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatActivity.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatActivity.java @@ -17,6 +17,8 @@ public class ChatActivity extends BaseActivity { public static final String EXTRA_CHAT_PEER = "chat_peer"; + private String quote; + private ChatFragment chatFragment; public static Intent build(Peer peer, Context context) { final Intent intent = new Intent(context, ChatActivity.class); @@ -42,14 +44,11 @@ public void onCreate(Bundle saveInstance) { // if (saveInstance == null) { Peer peer = Peer.fromUniqueId(getIntent().getExtras().getLong(EXTRA_CHAT_PEER)); - ChatFragment chatFragment = ChatFragment.create(peer); + chatFragment = ChatFragment.create(peer); getSupportFragmentManager().beginTransaction() .add(R.id.chatFragment, chatFragment) .commitNow(); - String quote = getIntent().getStringExtra("forward_text_raw"); - if (quote != null) { - chatFragment.onMessageQuote(quote); - } + quote = getIntent().getStringExtra("forward_text_raw"); } } @@ -66,6 +65,15 @@ public void onBackPressed() { } } + @Override + protected void onResume() { + super.onResume(); + if (quote != null) { + chatFragment.onMessageQuote(quote); + quote = null; + } + } + @Override public ActionMode startSupportActionMode(@NonNull final ActionMode.Callback callback) { // Fix for bug https://code.google.com/p/android/issues/detail?id=159527 From d765b43c59a20133d9c30fdc0a1823fe3f31f440 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Thu, 21 Jul 2016 02:05:28 +0300 Subject: [PATCH 076/414] fix(android): sendUri check mimeType for null --- .../src/main/java/im/actor/core/AndroidMessenger.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/actor-sdk/sdk-core/core/core-android/src/main/java/im/actor/core/AndroidMessenger.java b/actor-sdk/sdk-core/core/core-android/src/main/java/im/actor/core/AndroidMessenger.java index a78aae7ce7..6ddf47cf36 100644 --- a/actor-sdk/sdk-core/core/core-android/src/main/java/im/actor/core/AndroidMessenger.java +++ b/actor-sdk/sdk-core/core/core-android/src/main/java/im/actor/core/AndroidMessenger.java @@ -324,9 +324,6 @@ public Command sendUri(final Peer peer, final Uri uri) { picturePath = cursor.getString(cursor.getColumnIndex(filePathColumn[0])); mimeType = cursor.getString(cursor.getColumnIndex(filePathColumn[1])); fileName = cursor.getString(cursor.getColumnIndex(filePathColumn[2])); - if (mimeType == null) { - mimeType = "?/?"; - } cursor.close(); } else { picturePath = uri.getPath(); @@ -339,6 +336,10 @@ public Command sendUri(final Peer peer, final Uri uri) { } } + if (mimeType == null) { + mimeType = "?/?"; + } + if (picturePath == null || !uri.getScheme().equals("file")) { File externalFile = context.getExternalFilesDir(null); if (externalFile == null) { From bb976402da50e34985a9bdc2c2a5f89733ec1cf4 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Thu, 21 Jul 2016 12:04:48 +0300 Subject: [PATCH 077/414] feat(iOS): Updated GroupInfo controller --- .../ActorSDK.xcodeproj/project.pbxproj | 2 +- .../Resources/Base.lproj/Localizable.strings | 10 + .../Resources/es.lproj/Localizable.strings | 10 + .../Resources/pt.lproj/Localizable.strings | 10 + .../Resources/ru.lproj/Localizable.strings | 8 + .../zh-Hans.lproj/Localizable.strings | 8 + .../Group/AAGroupEditInfoViewController.swift | 34 ++ .../Group/AAGroupViewController.swift | 447 ++++++++++-------- .../AAContentTableController.swift | 2 +- 9 files changed, 339 insertions(+), 192 deletions(-) diff --git a/actor-sdk/sdk-core-ios/ActorSDK.xcodeproj/project.pbxproj b/actor-sdk/sdk-core-ios/ActorSDK.xcodeproj/project.pbxproj index 88db792410..ca90813ac2 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK.xcodeproj/project.pbxproj +++ b/actor-sdk/sdk-core-ios/ActorSDK.xcodeproj/project.pbxproj @@ -1236,8 +1236,8 @@ 066A52E81BC52A25000E606E /* Cells */, 066A52E31BC52A20000E606E /* AAAddParticipantViewController.swift */, 066A52E21BC52A20000E606E /* AAGroupViewController.swift */, - 066A52E41BC52A20000E606E /* AAInviteLinkViewController.swift */, 06ABFE371D3FCCE30031A0D6 /* AAGroupEditInfoViewController.swift */, + 066A52E41BC52A20000E606E /* AAInviteLinkViewController.swift */, ); path = Group; sourceTree = ""; diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/Base.lproj/Localizable.strings b/actor-sdk/sdk-core-ios/ActorSDK/Resources/Base.lproj/Localizable.strings index d689cea672..8af0acfa46 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Resources/Base.lproj/Localizable.strings +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/Base.lproj/Localizable.strings @@ -290,6 +290,12 @@ "GroupMembers" = "{0} MEMBERS"; +"GroupViewMembers" = "Members"; + +"GroupAbout" = "About"; + +"GroupAboutChannel" = "About"; + "GroupMemberAdmin" = "admin"; "GroupMemberInfo" = "Profile"; @@ -320,8 +326,12 @@ "GroupLeave" = "Leave group"; +"GroupLeaveChannel" = "Leave channel"; + "GroupLeaveConfirm" = "Are you sure you want to leave group?"; +"GroupLeaveConfirmChannel" = "Are you sure you want to leave channel?"; + "GroupLeaveConfirmAction" = "Leave"; "GroupInviteLinkPageTitle" = "Invite Link"; diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/es.lproj/Localizable.strings b/actor-sdk/sdk-core-ios/ActorSDK/Resources/es.lproj/Localizable.strings index 976879373a..aa698985ca 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Resources/es.lproj/Localizable.strings +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/es.lproj/Localizable.strings @@ -287,6 +287,12 @@ "GroupMembers" = "{0} MIEMBROS"; +"GroupViewMembers" = "Miembros"; + +"GroupAbout" = "About"; + +"GroupAboutChannel" = "About"; + "GroupMemberAdmin" = "admin"; "GroupMemberInfo" = "Perfil"; @@ -317,8 +323,12 @@ "GroupLeave" = "Salir de grupo"; +"GroupLeaveChannel" = "Leave channel"; + "GroupLeaveConfirm" = "¿Seguro que quieres dejar de grupo?"; +"GroupLeaveConfirmChannel" = "Are you sure you want to leave channel?"; + "GroupLeaveConfirmAction" = "Salir"; "GroupInviteLinkPageTitle" = "invitar por Enlace"; diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/pt.lproj/Localizable.strings b/actor-sdk/sdk-core-ios/ActorSDK/Resources/pt.lproj/Localizable.strings index e35ff1cafb..0196f12ad3 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Resources/pt.lproj/Localizable.strings +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/pt.lproj/Localizable.strings @@ -278,6 +278,12 @@ "GroupMembers" = "{0} MEMBROS"; +"GroupAbout" = "About"; + +"GroupAboutChannel" = "About"; + +"GroupViewMembers" = "Membros"; + "GroupMemberAdmin" = "admin"; "GroupMemberInfo" = "Perfil"; @@ -308,8 +314,12 @@ "GroupLeave" = "Sair do grupo"; +"GroupLeaveChannel" = "Leave channel"; + "GroupLeaveConfirm" = "Você tem certeza que quer sair do grupo?"; +"GroupLeaveConfirmChannel" = "Are you sure you want to leave channel?"; + "GroupLeaveConfirmAction" = "Sair"; "GroupInviteLinkPageTitle" = "Link de Convite"; diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/ru.lproj/Localizable.strings b/actor-sdk/sdk-core-ios/ActorSDK/Resources/ru.lproj/Localizable.strings index 894dc49ff9..de2c9a5dc8 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Resources/ru.lproj/Localizable.strings +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/ru.lproj/Localizable.strings @@ -290,6 +290,12 @@ "GroupMembers" = "{0} УЧАСТНИКИ"; +"GroupAbout" = "О группе"; + +"GroupAboutChannel" = "О канале"; + +"GroupViewMembers" = "Участники"; + "GroupMemberAdmin" = "админ."; "GroupMemberInfo" = "Профиль"; @@ -320,6 +326,8 @@ "GroupLeave" = "Покинуть группу"; +"GroupLeaveChannel" = "Leave channel"; + "GroupLeaveConfirm" = "Вы уверены, что хотите покинуть группу?"; "GroupLeaveConfirmAction" = "Покинуть"; diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/zh-Hans.lproj/Localizable.strings b/actor-sdk/sdk-core-ios/ActorSDK/Resources/zh-Hans.lproj/Localizable.strings index 857ad6e9ed..d4c935fe36 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Resources/zh-Hans.lproj/Localizable.strings +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/zh-Hans.lproj/Localizable.strings @@ -272,10 +272,16 @@ "GroupMembers" = "{0}个成员"; +"GroupViewMembers" = "个成员"; + "GroupMemberAdmin" = "管理员"; "GroupMemberInfo" = "成员信息"; +"GroupAbout" = "About"; + +"GroupAboutChannel" = "About"; + "GroupMemberMakeAdmin" = "设为群组管理员"; "GroupMemberMakeMessage" = "确定将 {name} 设为群组管理员吗?"; @@ -302,6 +308,8 @@ "GroupLeave" = "退出群组"; +"GroupLeaveChannel" = "Leave channel"; + "GroupLeaveConfirm" = "你确定要退出群组吗?"; "GroupLeaveConfirmAction" = "退出"; diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupEditInfoViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupEditInfoViewController.swift index 6a8dfc524d..b92beff15e 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupEditInfoViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupEditInfoViewController.swift @@ -6,6 +6,14 @@ import Foundation public class AAGroupEditInfoController: AAViewController { + private let scrollView = UIScrollView() + private let bgContainer = UIView() + private let topSeparator = UIView() + private let bottomSeparator = UIView() + + private let nameInput = UITextField() + private let separator = UIView() + public init(gid: Int) { super.init() self.gid = gid @@ -18,6 +26,32 @@ public class AAGroupEditInfoController: AAViewController { public override func viewDidLoad() { super.viewDidLoad() + view.backgroundColor = appStyle.vcBackyardColor + + scrollView.alwaysBounceVertical = true + + separator.backgroundColor = appStyle.vcSeparatorColor + topSeparator.backgroundColor = appStyle.vcSeparatorColor + bottomSeparator.backgroundColor = appStyle.vcSeparatorColor + + bgContainer.backgroundColor = appStyle.vcBgColor + + scrollView.addSubview(bgContainer) + bgContainer.addSubview(nameInput) + bgContainer.addSubview(separator) + bgContainer.addSubview(topSeparator) + bgContainer.addSubview(bottomSeparator) + view.addSubview(scrollView) + } + + public override func viewWillLayoutSubviews() { + super.viewWillLayoutSubviews() + scrollView.frame = CGRectMake(0, 0, view.width, view.height) + nameInput.frame = CGRectMake(72, 22, view.width - 72 - 10, 44) + separator.frame = CGRectMake(72, 66, view.width - 72, 0.5) + bgContainer.frame = CGRectMake(0, 0, view.width, 144) + topSeparator.frame = CGRectMake(0, 0, bgContainer.width, 0.5) + bottomSeparator.frame = CGRectMake(0, bgContainer.height - 0.5, bgContainer.width, 0.5) } } \ No newline at end of file diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewController.swift index 82659eb203..65a94f44ff 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewController.swift @@ -31,6 +31,13 @@ public class AAGroupViewController: AAContentTableController { public override func tableDidLoad() { + // NavigationBar + if self.group.isCanEditInfo.get().booleanValue() { + self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationEdit"), style: .Plain, target: self, action: #selector(editDidPressed)) + } else { + self.navigationItem.rightBarButtonItem = nil + } + // Header section { (s) -> () in @@ -38,11 +45,12 @@ public class AAGroupViewController: AAContentTableController { self.headerRow = s.avatar { (r) -> () in r.id = self.gid - r.subtitleHidden = true r.bindAction = { (r) -> () in r.avatar = self.group.getAvatarModel().get() r.title = self.group.getNameModel().get() + r.subtitle = Actor.getFormatter().formatGroupMembers(self.group.getMembersCountModel().get().intValue()) + self.group.getPresenceModel().get() } r.avatarDidTap = { (view) -> () in @@ -58,66 +66,75 @@ public class AAGroupViewController: AAContentTableController { } } - if self.group.isCanEditInfo.get().booleanValue() { - - // Header: Change photo - s.action("GroupSetPhoto") { (r) -> () in - r.selectAction = { () -> Bool in - let hasCamera = UIImagePickerController.isSourceTypeAvailable(UIImagePickerControllerSourceType.Camera) - self.showActionSheet( hasCamera ? ["PhotoCamera", "PhotoLibrary"] : ["PhotoLibrary"], - cancelButton: "AlertCancel", - destructButton: self.group.getAvatarModel().get() != nil ? "PhotoRemove" : nil, - sourceView: self.view, - sourceRect: self.view.bounds, - tapClosure: { (index) -> () in - if (index == -2) { - self.confirmAlertUser("PhotoRemoveGroupMessage", - action: "PhotoRemove", - tapYes: { () -> () in - Actor.removeGroupAvatarWithGid(jint(self.gid)) - }, tapNo: nil) - } else if (index >= 0) { - let takePhoto: Bool = (index == 0) && hasCamera - self.pickAvatar(takePhoto, closure: { (image) -> () in - Actor.changeGroupAvatar(jint(self.gid), image: image) - }) - } - }) - - return true - } - } - - // Header: Change title - s.action("GroupSetTitle") { (r) -> () in - r.selectAction = { () -> Bool in - self.startEditField { (c) -> () in - - c.title = "GroupEditHeader" - - c.fieldHint = "GroupEditHint" - - c.actionTitle = "NavigationSave" - - c.initialText = self.group.getNameModel().get() - - c.didDoneTap = { (t, c) -> () in - - if t.length == 0 { - return - } - - // c.executeSafeOnlySuccess(Actor.editGroupTitleCommandWithGid(jint(self.gid), withTitle: t)!, successBlock: { (val) -> Void in - // c.dismiss() - // }) - } - } - - return true - } + // About + if let about = self.group.about.get() { + if self.group.groupType == ACGroupType.CHANNEL() { + s.text("GroupAboutChannel", content: about) + } else { + s.text("GroupAbout", content: about) } } +// if self.group.isCanEditInfo.get().booleanValue() { +// +// // Header: Change photo +// s.action("GroupSetPhoto") { (r) -> () in +// r.selectAction = { () -> Bool in +// let hasCamera = UIImagePickerController.isSourceTypeAvailable(UIImagePickerControllerSourceType.Camera) +// self.showActionSheet( hasCamera ? ["PhotoCamera", "PhotoLibrary"] : ["PhotoLibrary"], +// cancelButton: "AlertCancel", +// destructButton: self.group.getAvatarModel().get() != nil ? "PhotoRemove" : nil, +// sourceView: self.view, +// sourceRect: self.view.bounds, +// tapClosure: { (index) -> () in +// if (index == -2) { +// self.confirmAlertUser("PhotoRemoveGroupMessage", +// action: "PhotoRemove", +// tapYes: { () -> () in +// Actor.removeGroupAvatarWithGid(jint(self.gid)) +// }, tapNo: nil) +// } else if (index >= 0) { +// let takePhoto: Bool = (index == 0) && hasCamera +// self.pickAvatar(takePhoto, closure: { (image) -> () in +// Actor.changeGroupAvatar(jint(self.gid), image: image) +// }) +// } +// }) +// +// return true +// } +// } +// +// // Header: Change title +// s.action("GroupSetTitle") { (r) -> () in +// r.selectAction = { () -> Bool in +// self.startEditField { (c) -> () in +// +// c.title = "GroupEditHeader" +// +// c.fieldHint = "GroupEditHint" +// +// c.actionTitle = "NavigationSave" +// +// c.initialText = self.group.getNameModel().get() +// +// c.didDoneTap = { (t, c) -> () in +// +// if t.length == 0 { +// return +// } +// +// // c.executeSafeOnlySuccess(Actor.editGroupTitleCommandWithGid(jint(self.gid), withTitle: t)!, successBlock: { (val) -> Void in +// // c.dismiss() +// // }) +// } +// } +// +// return true +// } +// } +// } + } // Calls @@ -170,26 +187,67 @@ public class AAGroupViewController: AAContentTableController { navigationController.modalInPopover = true navigationController.modalPresentationStyle = UIModalPresentationStyle.CurrentContext } - self.presentViewController(navigationController, animated: true, completion: { - } - ) + self.presentViewController(navigationController, animated: true, completion: nil) return false } } } } + + // View Members + if self.group.isCanViewMembers.get().booleanValue() && self.group.isAsyncMembers.get().booleanValue() { + //section { (s) -> () in + + s.common({ (r) -> () in + r.content = AALocalized("GroupViewMembers") + r.style = .Normal + r.selectAction = { () -> Bool in + // TODO: Implement + return false + } + }) + + s.action("GroupAddParticipant") { (r) -> () in + + r.selectAction = { () -> Bool in + let addParticipantController = AAAddParticipantViewController(gid: self.gid) + let navigationController = AANavigationController(rootViewController: addParticipantController) + if (AADevice.isiPad) { + navigationController.modalInPopover = true + navigationController.modalPresentationStyle = UIModalPresentationStyle.CurrentContext + } + self.presentViewController(navigationController, animated: true, completion: nil) + return false + } + } + //} + } + } + + // Leave group if group.isCanLeave.get().booleanValue() { - // Leave group section { (s) -> () in s.common({ (r) -> () in - r.content = AALocalized("GroupLeave") + + if self.group.groupType == ACGroupType.CHANNEL() { + r.content = AALocalized("GroupLeaveChannel") + } else { + r.content = AALocalized("GroupLeave") + } + r.style = .Destructive r.selectAction = { () -> Bool in - self.confirmDestructive(AALocalized("GroupLeaveConfirm"), action: AALocalized("GroupLeaveConfirmAction"), yes: { () -> () in + let title: String + if self.group.groupType == ACGroupType.CHANNEL() { + title = AALocalized("GroupLeaveConfirmChannel") + } else { + title = AALocalized("GroupLeaveConfirm") + } + self.confirmDestructive(title, action: AALocalized("GroupLeaveConfirmAction"), yes: { () -> () in // self.executeSafe(Actor.leaveGroupCommandWithGid(jint(self.gid))!) }) @@ -200,142 +258,146 @@ public class AAGroupViewController: AAContentTableController { } // Members - section { (s) -> () in - - s.autoSeparatorsInset = 65 - s.autoSeparatorTopOffset = 1 - s.headerHeight = 0 - - // Members: Header - s.header(AALocalized("GroupMembers").uppercaseString) + + if group.isCanViewMembers.get().booleanValue() && !group.isAsyncMembers.get().booleanValue() { - // Members: Add - s.action("GroupAddParticipant") { (r) -> () in + section { (s) -> () in - r.contentInset = 65 + s.autoSeparatorsInset = 65 + s.autoSeparatorTopOffset = 1 + s.headerHeight = 0 - r.selectAction = { () -> Bool in - let addParticipantController = AAAddParticipantViewController(gid: self.gid) - let navigationController = AANavigationController(rootViewController: addParticipantController) - if (AADevice.isiPad) { - navigationController.modalInPopover = true - navigationController.modalPresentationStyle = UIModalPresentationStyle.CurrentContext - } - self.presentViewController(navigationController, animated: true, completion: nil) - return false - } - } - - // Members: List - self.memberRows = s.arrays { (r: AAManagedArrayRows) -> () in - r.height = 48 - r.data = self.group.members.get().toArray().toSwiftArray() - r.data.sortInPlace(self.membersSort) + // Members: Header + s.header(AALocalized("GroupMembers").uppercaseString) - r.bindData = { (c, d) -> () in - let user = Actor.getUserWithUid(d.uid) - c.bind(user, isAdmin: d.isAdministrator) + // Members: Add + s.action("GroupAddParticipant") { (r) -> () in - // Notify to request onlines - Actor.onUserVisibleWithUid(d.uid) + r.contentInset = 65 + + r.selectAction = { () -> Bool in + let addParticipantController = AAAddParticipantViewController(gid: self.gid) + let navigationController = AANavigationController(rootViewController: addParticipantController) + if (AADevice.isiPad) { + navigationController.modalInPopover = true + navigationController.modalPresentationStyle = UIModalPresentationStyle.CurrentContext + } + self.presentViewController(navigationController, animated: true, completion: nil) + return false + } } - r.selectAction = { (d) -> Bool in - let user = Actor.getUserWithUid(d.uid) - if (user.getId() == Actor.myUid()) { - return true - } - - let name = user.getNameModel().get() + // Members: List + self.memberRows = s.arrays { (r: AAManagedArrayRows) -> () in + r.height = 48 + r.data = self.group.members.get().toArray().toSwiftArray() + r.data.sortInPlace(self.membersSort) - self.alertSheet { (a: AAAlertSetting) -> () in + r.bindData = { (c, d) -> () in + let user = Actor.getUserWithUid(d.uid) + c.bind(user, isAdmin: d.isAdministrator) - a.cancel = "AlertCancel" - - a.action("GroupMemberInfo") { () -> () in - var controller: AAViewController! = ActorSDK.sharedActor().delegate.actorControllerForUser(Int(user.getId())) - if controller == nil { - controller = AAUserViewController(uid: Int(user.getId())) - } - self.navigateNext(controller, removeCurrent: false) + // Notify to request onlines + Actor.onUserVisibleWithUid(d.uid) + } + + r.selectAction = { (d) -> Bool in + let user = Actor.getUserWithUid(d.uid) + if (user.getId() == Actor.myUid()) { + return true } - a.action("GroupMemberWrite") { () -> () in - if let customController = ActorSDK.sharedActor().delegate.actorControllerForConversation(ACPeer.userWithInt(user.getId())) { - self.navigateDetail(customController) - } else { - self.navigateDetail(ConversationViewController(peer: ACPeer.userWithInt(user.getId()))) - } - self.popover?.dismissPopoverAnimated(true) - } + let name = user.getNameModel().get() - a.action("GroupMemberCall", closure: { () -> () in - let phones = user.getPhonesModel().get() - if phones.size() == 0 { - self.alertUser("GroupMemberCallNoPhones") - } else if phones.size() == 1 { - let number = phones.getWithInt(0) - ActorSDK.sharedActor().openUrl("telprompt://+\(number.phone)") - } else { - - var numbers = [String]() - for i in 0.. () in + + a.cancel = "AlertCancel" + + a.action("GroupMemberInfo") { () -> () in + var controller: AAViewController! = ActorSDK.sharedActor().delegate.actorControllerForUser(Int(user.getId())) + if controller == nil { + controller = AAUserViewController(uid: Int(user.getId())) } - self.showActionSheet(numbers, - cancelButton: "AlertCancel", - destructButton: nil, - sourceView: self.view, - sourceRect: self.view.bounds, - tapClosure: { (index) -> () in - if (index >= 0) { - let number = phones.getWithInt(jint(index)) - ActorSDK.sharedActor().openUrl("telprompt://+\(number.phone)") - } - }) + self.navigateNext(controller, removeCurrent: false) } - }) + + a.action("GroupMemberWrite") { () -> () in + if let customController = ActorSDK.sharedActor().delegate.actorControllerForConversation(ACPeer.userWithInt(user.getId())) { + self.navigateDetail(customController) + } else { + self.navigateDetail(ConversationViewController(peer: ACPeer.userWithInt(user.getId()))) + } + self.popover?.dismissPopoverAnimated(true) + } + + a.action("GroupMemberCall", closure: { () -> () in + let phones = user.getPhonesModel().get() + if phones.size() == 0 { + self.alertUser("GroupMemberCallNoPhones") + } else if phones.size() == 1 { + let number = phones.getWithInt(0) + ActorSDK.sharedActor().openUrl("telprompt://+\(number.phone)") + } else { + + var numbers = [String]() + for i in 0.. () in + if (index >= 0) { + let number = phones.getWithInt(jint(index)) + ActorSDK.sharedActor().openUrl("telprompt://+\(number.phone)") + } + }) + } + }) + + // // Detect if we are admin + // let members: [ACGroupMember] = self.group.members.get().toArray().toSwiftArray() + // var isAdmin = self.group.creatorId == Actor.myUid() + // if !isAdmin { + // for m in members { + // if m.uid == Actor.myUid() { + // isAdmin = m.isAdministrator + // } + // } + // } + // + // // Can mark as admin + // let canMarkAdmin = isAdmin && !d.isAdministrator + // + // if canMarkAdmin { + // a.action("GroupMemberMakeAdmin") { () -> () in + // + // self.confirmDestructive(AALocalized("GroupMemberMakeMessage").replace("{name}", dest: name), action: AALocalized("GroupMemberMakeAction")) { + // + // self.executeSafe(Actor.makeAdminCommandWithGid(jint(self.gid), withUid: jint(user.getId()))!) + // } + // } + // } + + // Can kick user + // let canKick = isAdmin || d.inviterUid == Actor.myUid() + // + // if canKick { + // a.destructive("GroupMemberKick") { () -> () in + // self.confirmDestructive(AALocalized("GroupMemberKickMessage") + // .replace("{name}", dest: name), action: AALocalized("GroupMemberKickAction")) { + // + // self.executeSafe(Actor.kickMemberCommandWithGid(jint(self.gid), withUid: user.getId())!) + // } + // } + // } + } -// // Detect if we are admin -// let members: [ACGroupMember] = self.group.members.get().toArray().toSwiftArray() -// var isAdmin = self.group.creatorId == Actor.myUid() -// if !isAdmin { -// for m in members { -// if m.uid == Actor.myUid() { -// isAdmin = m.isAdministrator -// } -// } -// } -// -// // Can mark as admin -// let canMarkAdmin = isAdmin && !d.isAdministrator -// -// if canMarkAdmin { -// a.action("GroupMemberMakeAdmin") { () -> () in -// -// self.confirmDestructive(AALocalized("GroupMemberMakeMessage").replace("{name}", dest: name), action: AALocalized("GroupMemberMakeAction")) { -// -// self.executeSafe(Actor.makeAdminCommandWithGid(jint(self.gid), withUid: jint(user.getId()))!) -// } -// } -// } - - // Can kick user -// let canKick = isAdmin || d.inviterUid == Actor.myUid() -// -// if canKick { -// a.destructive("GroupMemberKick") { () -> () in -// self.confirmDestructive(AALocalized("GroupMemberKickMessage") -// .replace("{name}", dest: name), action: AALocalized("GroupMemberKickAction")) { -// -// self.executeSafe(Actor.kickMemberCommandWithGid(jint(self.gid), withUid: user.getId())!) -// } -// } -// } + return true } - - return true } } } @@ -354,12 +416,13 @@ public class AAGroupViewController: AAContentTableController { } // Bind members - - binder.bind(group.getMembersModel()) { (value: JavaUtilHashSet?) -> () in - if let v = value { - self.memberRows.data = v.toArray().toSwiftArray() - self.memberRows.data.sortInPlace(self.membersSort) - self.memberRows.reload() + if memberRows != nil{ + binder.bind(group.getMembersModel()) { (value: JavaUtilHashSet?) -> () in + if let v = value { + self.memberRows.data = v.toArray().toSwiftArray() + self.memberRows.data.sortInPlace(self.membersSort) + self.memberRows.reload() + } } } @@ -380,4 +443,8 @@ public class AAGroupViewController: AAContentTableController { } } } + + public func editDidPressed() { + self.navigateNext(AAGroupEditInfoController(gid: gid)) + } } \ No newline at end of file diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/AAContentTableController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/AAContentTableController.swift index 4f245fe2e6..6849f816d7 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/AAContentTableController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/AAContentTableController.swift @@ -33,7 +33,7 @@ public class AAContentTableController: AAManagedTableController, AAManagedTableC // DSL Implementation - public func section(closure: (s: AAManagedSection) -> ()) { + public func section(@noescape closure: (s: AAManagedSection) -> ()) { if !isInLoad { fatalError("Unable to change sections not during tableDidLoad method call") } From d4c2786ea141a1c537be8cc6d66701ce65ce3376 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Thu, 21 Jul 2016 12:07:43 +0300 Subject: [PATCH 078/414] feat(core): Updated API --- .../main/java/im/actor/core/api/ApiGroup.java | 14 ++- .../actor/core/api/ApiPeerSearchResult.java | 90 ++----------------- .../actor/core/api/parser/UpdatesParser.java | 1 + .../core/api/updates/UpdateGroupDeleted.java | 61 +++++++++++++ 4 files changed, 84 insertions(+), 82 deletions(-) create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupDeleted.java diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiGroup.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiGroup.java index 95963d3e5e..8d63742b79 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiGroup.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiGroup.java @@ -25,9 +25,10 @@ public class ApiGroup extends BserObject { private Boolean isHidden; private ApiGroupType groupType; private Boolean canSendMessage; + private Boolean isDeleted; private ApiMapValue ext; - public ApiGroup(int id, long accessHash, @NotNull String title, @Nullable ApiAvatar avatar, @Nullable Integer membersCount, @Nullable Boolean isMember, @Nullable Boolean isHidden, @Nullable ApiGroupType groupType, @Nullable Boolean canSendMessage, @Nullable ApiMapValue ext) { + public ApiGroup(int id, long accessHash, @NotNull String title, @Nullable ApiAvatar avatar, @Nullable Integer membersCount, @Nullable Boolean isMember, @Nullable Boolean isHidden, @Nullable ApiGroupType groupType, @Nullable Boolean canSendMessage, @Nullable Boolean isDeleted, @Nullable ApiMapValue ext) { this.id = id; this.accessHash = accessHash; this.title = title; @@ -37,6 +38,7 @@ public ApiGroup(int id, long accessHash, @NotNull String title, @Nullable ApiAva this.isHidden = isHidden; this.groupType = groupType; this.canSendMessage = canSendMessage; + this.isDeleted = isDeleted; this.ext = ext; } @@ -87,6 +89,11 @@ public Boolean canSendMessage() { return this.canSendMessage; } + @Nullable + public Boolean isDeleted() { + return this.isDeleted; + } + @Nullable public ApiMapValue getExt() { return this.ext; @@ -106,6 +113,7 @@ public void parse(BserValues values) throws IOException { this.groupType = ApiGroupType.parse(val_groupType); } this.canSendMessage = values.optBool(26); + this.isDeleted = values.optBool(27); this.ext = values.optObj(22, new ApiMapValue()); if (values.hasRemaining()) { setUnmappedObjects(values.buildRemaining()); @@ -138,6 +146,9 @@ public void serialize(BserWriter writer) throws IOException { if (this.canSendMessage != null) { writer.writeBool(26, this.canSendMessage); } + if (this.isDeleted != null) { + writer.writeBool(27, this.isDeleted); + } if (this.ext != null) { writer.writeObject(22, this.ext); } @@ -161,6 +172,7 @@ public String toString() { res += ", isHidden=" + this.isHidden; res += ", groupType=" + this.groupType; res += ", canSendMessage=" + this.canSendMessage; + res += ", isDeleted=" + this.isDeleted; res += ", ext=" + this.ext; res += "}"; return res; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiPeerSearchResult.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiPeerSearchResult.java index 2db3b2d8fb..a12c4a85a6 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiPeerSearchResult.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiPeerSearchResult.java @@ -17,23 +17,11 @@ public class ApiPeerSearchResult extends BserObject { private ApiPeer peer; - private String title; - private String description; - private Integer membersCount; - private Long dateCreated; - private Integer creator; - private Boolean isPublic; - private Boolean isJoined; + private String optMatchString; - public ApiPeerSearchResult(@NotNull ApiPeer peer, @NotNull String title, @Nullable String description, @Nullable Integer membersCount, @Nullable Long dateCreated, @Nullable Integer creator, @Nullable Boolean isPublic, @Nullable Boolean isJoined) { + public ApiPeerSearchResult(@NotNull ApiPeer peer, @Nullable String optMatchString) { this.peer = peer; - this.title = title; - this.description = description; - this.membersCount = membersCount; - this.dateCreated = dateCreated; - this.creator = creator; - this.isPublic = isPublic; - this.isJoined = isJoined; + this.optMatchString = optMatchString; } public ApiPeerSearchResult() { @@ -45,51 +33,15 @@ public ApiPeer getPeer() { return this.peer; } - @NotNull - public String getTitle() { - return this.title; - } - @Nullable - public String getDescription() { - return this.description; - } - - @Nullable - public Integer getMembersCount() { - return this.membersCount; - } - - @Nullable - public Long getDateCreated() { - return this.dateCreated; - } - - @Nullable - public Integer getCreator() { - return this.creator; - } - - @Nullable - public Boolean isPublic() { - return this.isPublic; - } - - @Nullable - public Boolean isJoined() { - return this.isJoined; + public String getOptMatchString() { + return this.optMatchString; } @Override public void parse(BserValues values) throws IOException { this.peer = values.getObj(1, new ApiPeer()); - this.title = values.getString(2); - this.description = values.optString(3); - this.membersCount = values.optInt(4); - this.dateCreated = values.optLong(5); - this.creator = values.optInt(6); - this.isPublic = values.optBool(7); - this.isJoined = values.optBool(8); + this.optMatchString = values.optString(3); } @Override @@ -98,27 +50,8 @@ public void serialize(BserWriter writer) throws IOException { throw new IOException(); } writer.writeObject(1, this.peer); - if (this.title == null) { - throw new IOException(); - } - writer.writeString(2, this.title); - if (this.description != null) { - writer.writeString(3, this.description); - } - if (this.membersCount != null) { - writer.writeInt(4, this.membersCount); - } - if (this.dateCreated != null) { - writer.writeLong(5, this.dateCreated); - } - if (this.creator != null) { - writer.writeInt(6, this.creator); - } - if (this.isPublic != null) { - writer.writeBool(7, this.isPublic); - } - if (this.isJoined != null) { - writer.writeBool(8, this.isJoined); + if (this.optMatchString != null) { + writer.writeString(3, this.optMatchString); } } @@ -126,12 +59,7 @@ public void serialize(BserWriter writer) throws IOException { public String toString() { String res = "struct PeerSearchResult{"; res += "peer=" + this.peer; - res += ", title=" + this.title; - res += ", description=" + this.description; - res += ", membersCount=" + this.membersCount; - res += ", dateCreated=" + this.dateCreated; - res += ", creator=" + this.creator; - res += ", isPublic=" + this.isPublic; + res += ", optMatchString=" + this.optMatchString; res += "}"; return res; } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/parser/UpdatesParser.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/parser/UpdatesParser.java index a372a3cf8a..9963a12115 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/parser/UpdatesParser.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/parser/UpdatesParser.java @@ -69,6 +69,7 @@ public Update read(int type, byte[] payload) throws IOException { case 2647: return UpdateGroupCanLeaveChanged.fromBytes(payload); case 2648: return UpdateGroupCanDeleteChanged.fromBytes(payload); case 2641: return UpdateGroupCanEditAdminSettingsChanged.fromBytes(payload); + case 2658: return UpdateGroupDeleted.fromBytes(payload); case 2612: return UpdateGroupMemberChanged.fromBytes(payload); case 2615: return UpdateGroupMembersBecameAsync.fromBytes(payload); case 2614: return UpdateGroupMembersUpdated.fromBytes(payload); diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupDeleted.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupDeleted.java new file mode 100644 index 0000000000..f1603eb091 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupDeleted.java @@ -0,0 +1,61 @@ +package im.actor.core.api.updates; +/* + * Generated by the Actor API Scheme generator. DO NOT EDIT! + */ + +import im.actor.runtime.bser.*; +import im.actor.runtime.collections.*; +import static im.actor.runtime.bser.Utils.*; +import im.actor.core.network.parser.*; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.NotNull; +import com.google.j2objc.annotations.ObjectiveCName; +import java.io.IOException; +import java.util.List; +import java.util.ArrayList; +import im.actor.core.api.*; + +public class UpdateGroupDeleted extends Update { + + public static final int HEADER = 0xa62; + public static UpdateGroupDeleted fromBytes(byte[] data) throws IOException { + return Bser.parse(new UpdateGroupDeleted(), data); + } + + private int groupId; + + public UpdateGroupDeleted(int groupId) { + this.groupId = groupId; + } + + public UpdateGroupDeleted() { + + } + + public int getGroupId() { + return this.groupId; + } + + @Override + public void parse(BserValues values) throws IOException { + this.groupId = values.getInt(1); + } + + @Override + public void serialize(BserWriter writer) throws IOException { + writer.writeInt(1, this.groupId); + } + + @Override + public String toString() { + String res = "update GroupDeleted{"; + res += "groupId=" + this.groupId; + res += "}"; + return res; + } + + @Override + public int getHeaderKey() { + return HEADER; + } +} From 61789de5a7906d298c621d5e6b02e0f11e97ebf4 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Thu, 21 Jul 2016 12:26:08 +0300 Subject: [PATCH 079/414] feat(scheme): Mask-based permissions --- actor-sdk/sdk-api/actor.json | 541 ++--------------- .../models/im/actor/api/scheme.mps | 574 +++++------------- 2 files changed, 196 insertions(+), 919 deletions(-) diff --git a/actor-sdk/sdk-api/actor.json b/actor-sdk/sdk-api/actor.json index 36aed64ab7..40857ff0eb 100644 --- a/actor-sdk/sdk-api/actor.json +++ b/actor-sdk/sdk-api/actor.json @@ -7882,6 +7882,20 @@ "name": "Group", "doc": [ "Group information", + "", + "Permissions.", + "Permissions of this structure is about group messages operation, such as", + "ability to send messages, clear chat, leave group and so on. This operations", + "Can be held outside of the Group Info page.", + "", + "Default value is ZERO, Opposide iz ONE. If Default is FALSE then ONE == TRUE.", + "If default is TRUE then ONE == FALSE.", + "Bits:", + "0 - canSendMessage. Default is FALSE.", + "1 - canClear. Default is FALSE.", + "2 - canLeave. Default is FALSE.", + "3 - canDelete. Default is FALSE.", + "", { "type": "reference", "argument": "id", @@ -7932,9 +7946,9 @@ }, { "type": "reference", - "argument": "canSendMessage", + "argument": "permissions", "category": "full", - "description": " Can user send messages. Default is equals isMember for Group and false for others." + "description": " Permissions of group object" }, { "type": "reference", @@ -8054,10 +8068,10 @@ { "type": { "type": "opt", - "childType": "bool" + "childType": "int64" }, "id": 26, - "name": "canSendMessage" + "name": "permissions" }, { "type": { @@ -8144,6 +8158,22 @@ "name": "GroupFull", "doc": [ "Goup Full information", + "Permissions.", + "Idea of Group Full mermissions is about Group Info pages. This permissions", + "are usefull only when trying to view and update group settings and not related", + "to chat messages itself.", + "Default value is ZERO, Opposide iz ONE. If Default is FALSE then ONE == TRUE.", + "If default is TRUE then ONE == FALSE.", + "Bits:", + "0 - canEditInfo. Default is FALSE.", + "1 - canViewMembers. Default is FALSE.", + "2 - canInviteMembers. Default is FALSE.", + "3 - canInviteViaLink. Default is FALSE.", + "4 - canCall. Default is FALSE.", + "5 - canEditAdminSettings. Default is FALSE.", + "6 - canViewAdmins. Default is FALSE.", + "7 - canEditAdmins. Default is FALSE.", + "", { "type": "reference", "argument": "id", @@ -8186,30 +8216,12 @@ "category": "full", "description": " Is Members need to be loaded asynchronous. Default is false." }, - { - "type": "reference", - "argument": "canViewMembers", - "category": "full", - "description": " Can current user view members of the group. Default is true." - }, - { - "type": "reference", - "argument": "canInvitePeople", - "category": "full", - "description": " Can current user invite new people. Default is true." - }, { "type": "reference", "argument": "isSharedHistory", "category": "full", "description": " Is history shared among all users. Default is false." }, - { - "type": "reference", - "argument": "canEditGroupInfo", - "category": "full", - "description": " If current user can edit group info. Default is true." - }, { "type": "reference", "argument": "shortName", @@ -8218,45 +8230,9 @@ }, { "type": "reference", - "argument": "canEditShortName", - "category": "full", - "description": " If not set only owner can edit group's short name" - }, - { - "type": "reference", - "argument": "canEditAdminList", - "category": "full", - "description": " If not set only owner can edit admin list" - }, - { - "type": "reference", - "argument": "canViewAdminList", - "category": "full", - "description": " If not set only owner and admins can view admin list" - }, - { - "type": "reference", - "argument": "canEditAdminSettings", + "argument": "permissions", "category": "full", - "description": " If not set only owner can edit admin settings" - }, - { - "type": "reference", - "argument": "canInviteViaLink", - "category": "full", - "description": " If user can invite via link, default is false" - }, - { - "type": "reference", - "argument": "canDelete", - "category": "full", - "description": " If user can delete this group, default is false" - }, - { - "type": "reference", - "argument": "canLeave", - "category": "hidden", - "description": " If user can leave this group, default is true" + "description": " Group Permissions" } ], "expandable": "true", @@ -8334,22 +8310,6 @@ "id": 11, "name": "isAsyncMembers" }, - { - "type": { - "type": "opt", - "childType": "bool" - }, - "id": 8, - "name": "canViewMembers" - }, - { - "type": { - "type": "opt", - "childType": "bool" - }, - "id": 9, - "name": "canInvitePeople" - }, { "type": { "type": "opt", @@ -8358,14 +8318,6 @@ "id": 10, "name": "isSharedHistory" }, - { - "type": { - "type": "opt", - "childType": "bool" - }, - "id": 13, - "name": "canEditGroupInfo" - }, { "type": { "type": "opt", @@ -8377,58 +8329,10 @@ { "type": { "type": "opt", - "childType": "bool" - }, - "id": 15, - "name": "canEditShortName" - }, - { - "type": { - "type": "opt", - "childType": "bool" - }, - "id": 16, - "name": "canEditAdminList" - }, - { - "type": { - "type": "opt", - "childType": "bool" - }, - "id": 17, - "name": "canViewAdminList" - }, - { - "type": { - "type": "opt", - "childType": "bool" - }, - "id": 18, - "name": "canEditAdminSettings" - }, - { - "type": { - "type": "opt", - "childType": "bool" - }, - "id": 19, - "name": "canInviteViaLink" - }, - { - "type": { - "type": "opt", - "childType": "bool" - }, - "id": 20, - "name": "canDelete" - }, - { - "type": { - "type": "opt", - "childType": "bool" + "childType": "int64" }, - "id": 21, - "name": "canLeave" + "id": 27, + "name": "permissions" } ] } @@ -8936,317 +8840,15 @@ { "type": "update", "content": { - "name": "GroupCanSendMessagesChanged", - "header": 2624, - "doc": [ - "Update about can send messages changed", - { - "type": "reference", - "argument": "groupId", - "category": "full", - "description": " Group Id" - }, - { - "type": "reference", - "argument": "canSendMessages", - "category": "full", - "description": " Can send messages" - } - ], - "attributes": [ - { - "type": { - "type": "alias", - "childType": "groupId" - }, - "id": 1, - "name": "groupId" - }, - { - "type": "bool", - "id": 2, - "name": "canSendMessages" - } - ] - } - }, - { - "type": "update", - "content": { - "name": "GroupCanViewMembersChanged", - "header": 2625, - "doc": [ - "Update about can view members changed", - { - "type": "reference", - "argument": "groupId", - "category": "full", - "description": " Group Id" - }, - { - "type": "reference", - "argument": "canViewMembers", - "category": "full", - "description": " Can view members" - } - ], - "attributes": [ - { - "type": { - "type": "alias", - "childType": "groupId" - }, - "id": 1, - "name": "groupId" - }, - { - "type": "bool", - "id": 2, - "name": "canViewMembers" - } - ] - } - }, - { - "type": "update", - "content": { - "name": "GroupCanInviteMembersChanged", - "header": 2626, - "doc": [ - "Update about can invite members changed", - { - "type": "reference", - "argument": "groupId", - "category": "full", - "description": " Group Id" - }, - { - "type": "reference", - "argument": "canInviteMembers", - "category": "full", - "description": " Can invite members" - } - ], - "attributes": [ - { - "type": { - "type": "alias", - "childType": "groupId" - }, - "id": 1, - "name": "groupId" - }, - { - "type": "bool", - "id": 2, - "name": "canInviteMembers" - } - ] - } - }, - { - "type": "update", - "content": { - "name": "GroupCanEditInfoChanged", - "header": 2631, - "doc": [ - "Update about can edit changed", - { - "type": "reference", - "argument": "groupId", - "category": "full", - "description": " Group Id" - }, - { - "type": "reference", - "argument": "canEditGroup", - "category": "full", - "description": " Can edit group info" - } - ], - "attributes": [ - { - "type": { - "type": "alias", - "childType": "groupId" - }, - "id": 1, - "name": "groupId" - }, - { - "type": "bool", - "id": 2, - "name": "canEditGroup" - } - ] - } - }, - { - "type": "update", - "content": { - "name": "GroupCanEditUsernameChanged", - "header": 2632, - "doc": [ - "Update about can edit username changed", - { - "type": "reference", - "argument": "groupId", - "category": "full", - "description": " Group Id" - }, - { - "type": "reference", - "argument": "canEditUsername", - "category": "full", - "description": " Can edit username" - } - ], - "attributes": [ - { - "type": { - "type": "alias", - "childType": "groupId" - }, - "id": 1, - "name": "groupId" - }, - { - "type": "bool", - "id": 2, - "name": "canEditUsername" - } - ] - } - }, - { - "type": "update", - "content": { - "name": "GroupCanEditAdminsChanged", - "header": 2633, - "doc": [ - "Update about can edit admins changed", - { - "type": "reference", - "argument": "groupId", - "category": "full", - "description": " Group Id" - }, - { - "type": "reference", - "argument": "canAssignAdmins", - "category": "hidden", - "description": " Can assign admins" - } - ], - "attributes": [ - { - "type": { - "type": "alias", - "childType": "groupId" - }, - "id": 1, - "name": "groupId" - }, - { - "type": "bool", - "id": 2, - "name": "canAssignAdmins" - } - ] - } - }, - { - "type": "update", - "content": { - "name": "GroupCanViewAdminsChanged", - "header": 2640, - "doc": [ - "Update about view admings changed", - { - "type": "reference", - "argument": "groupId", - "category": "full", - "description": " Group Id" - }, - { - "type": "reference", - "argument": "canViewAdmins", - "category": "hidden", - "description": " Can view admins" - } - ], - "attributes": [ - { - "type": { - "type": "alias", - "childType": "groupId" - }, - "id": 1, - "name": "groupId" - }, - { - "type": "bool", - "id": 2, - "name": "canViewAdmins" - } - ] - } - }, - { - "type": "update", - "content": { - "name": "GroupCanInviteViaLink", - "header": 2646, - "doc": [ - "Update about can invite via link changed", - { - "type": "reference", - "argument": "groupId", - "category": "full", - "description": " Group Id" - }, - { - "type": "reference", - "argument": "canInviteViaLink", - "category": "full", - "description": " Can Invite Via link" - } - ], - "attributes": [ - { - "type": { - "type": "alias", - "childType": "groupId" - }, - "id": 1, - "name": "groupId" - }, - { - "type": "bool", - "id": 2, - "name": "canInviteViaLink" - } - ] - } - }, - { - "type": "update", - "content": { - "name": "GroupCanLeaveChanged", - "header": 2647, + "name": "GroupDeleted", + "header": 2658, "doc": [ - "Update about can leave changed", + "Update about group deleted", { "type": "reference", "argument": "groupId", "category": "full", "description": " Group Id" - }, - { - "type": "reference", - "argument": "canLeaveChanged", - "category": "full", - "description": " Can leave changed" } ], "attributes": [ @@ -9257,11 +8859,6 @@ }, "id": 1, "name": "groupId" - }, - { - "type": "bool", - "id": 2, - "name": "canLeaveChanged" } ] } @@ -9269,10 +8866,10 @@ { "type": "update", "content": { - "name": "GroupCanDeleteChanged", - "header": 2648, + "name": "GroupPermissionsChanged", + "header": 2663, "doc": [ - "Update about can delete changed", + "Update about group permissions changed", { "type": "reference", "argument": "groupId", @@ -9281,9 +8878,9 @@ }, { "type": "reference", - "argument": "canDeleteChanged", + "argument": "permissions", "category": "full", - "description": " Can delete changed" + "description": " New Permissions" } ], "attributes": [ @@ -9296,9 +8893,9 @@ "name": "groupId" }, { - "type": "bool", + "type": "int64", "id": 2, - "name": "canDeleteChanged" + "name": "permissions" } ] } @@ -9306,10 +8903,10 @@ { "type": "update", "content": { - "name": "GroupCanEditAdminSettingsChanged", - "header": 2641, + "name": "GroupFullPermissionsChanged", + "header": 2664, "doc": [ - "Update about edit admin settings changed", + "Update about Full Group permissions changed", { "type": "reference", "argument": "groupId", @@ -9318,9 +8915,9 @@ }, { "type": "reference", - "argument": "canEditAdminSettings", + "argument": "permissions", "category": "full", - "description": " Can edit admin settings" + "description": " New Permissions" } ], "attributes": [ @@ -9333,35 +8930,9 @@ "name": "groupId" }, { - "type": "bool", + "type": "int64", "id": 2, - "name": "canEditAdminSettings" - } - ] - } - }, - { - "type": "update", - "content": { - "name": "GroupDeleted", - "header": 2658, - "doc": [ - "Update about group deleted", - { - "type": "reference", - "argument": "groupId", - "category": "full", - "description": " Group Id" - } - ], - "attributes": [ - { - "type": { - "type": "alias", - "childType": "groupId" - }, - "id": 1, - "name": "groupId" + "name": "permissions" } ] } diff --git a/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps b/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps index d447bbbd88..1c08c51b9f 100644 --- a/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps +++ b/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps @@ -6927,6 +6927,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -6969,8 +7011,8 @@ - - + + @@ -7068,9 +7110,9 @@ - + - + @@ -7205,20 +7247,6 @@ - - - - - - - - - - - - - - @@ -7226,13 +7254,6 @@ - - - - - - - @@ -7240,57 +7261,63 @@ - - - - - + + + + + - - - - - - + + - - - - - - + + - - - - - - + + - - - - - - + + - - - - - - + + - - - - - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -7327,64 +7354,20 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - + + @@ -7795,364 +7778,87 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + - + - - - - - - - + + - - + + - + - - - - - - + - - - + + + - + - + - - + + - - + + - - + + - + - + - - + + - + - - - + + + - + - + - - - - - + + - - + + - - - - + + - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - + + - + From e0ac8321da7bb45fb6fe833447edeab4e8024977 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Thu, 21 Jul 2016 14:08:30 +0300 Subject: [PATCH 080/414] feat(iOS): Showing Description of a group --- .../Resources/Base.lproj/Localizable.strings | 4 +--- .../Resources/es.lproj/Localizable.strings | 4 +--- .../Resources/pt.lproj/Localizable.strings | 4 +--- .../Resources/ru.lproj/Localizable.strings | 4 +--- .../zh-Hans.lproj/Localizable.strings | 4 +--- .../Group/AAGroupViewController.swift | 21 ++++++++++++------- .../Managed Runtime/ManagedCells.swift | 14 ++++++++++--- .../Sources/Views/Cells/AATextCell.swift | 19 +++++++++++++---- .../main/java/im/actor/core/entity/Group.java | 8 +++++++ 9 files changed, 52 insertions(+), 30 deletions(-) diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/Base.lproj/Localizable.strings b/actor-sdk/sdk-core-ios/ActorSDK/Resources/Base.lproj/Localizable.strings index 8af0acfa46..3f6f1ddeb1 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Resources/Base.lproj/Localizable.strings +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/Base.lproj/Localizable.strings @@ -292,9 +292,7 @@ "GroupViewMembers" = "Members"; -"GroupAbout" = "About"; - -"GroupAboutChannel" = "About"; +"GroupDescription" = "Description"; "GroupMemberAdmin" = "admin"; diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/es.lproj/Localizable.strings b/actor-sdk/sdk-core-ios/ActorSDK/Resources/es.lproj/Localizable.strings index aa698985ca..104d10d549 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Resources/es.lproj/Localizable.strings +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/es.lproj/Localizable.strings @@ -289,9 +289,7 @@ "GroupViewMembers" = "Miembros"; -"GroupAbout" = "About"; - -"GroupAboutChannel" = "About"; +"GroupDescription" = "Description"; "GroupMemberAdmin" = "admin"; diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/pt.lproj/Localizable.strings b/actor-sdk/sdk-core-ios/ActorSDK/Resources/pt.lproj/Localizable.strings index 0196f12ad3..fed6108fc9 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Resources/pt.lproj/Localizable.strings +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/pt.lproj/Localizable.strings @@ -278,9 +278,7 @@ "GroupMembers" = "{0} MEMBROS"; -"GroupAbout" = "About"; - -"GroupAboutChannel" = "About"; +"GroupDescription" = "Description"; "GroupViewMembers" = "Membros"; diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/ru.lproj/Localizable.strings b/actor-sdk/sdk-core-ios/ActorSDK/Resources/ru.lproj/Localizable.strings index de2c9a5dc8..e9f1dd2f9c 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Resources/ru.lproj/Localizable.strings +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/ru.lproj/Localizable.strings @@ -290,9 +290,7 @@ "GroupMembers" = "{0} УЧАСТНИКИ"; -"GroupAbout" = "О группе"; - -"GroupAboutChannel" = "О канале"; +"GroupDescription" = "Описание"; "GroupViewMembers" = "Участники"; diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/zh-Hans.lproj/Localizable.strings b/actor-sdk/sdk-core-ios/ActorSDK/Resources/zh-Hans.lproj/Localizable.strings index d4c935fe36..828ba280c5 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Resources/zh-Hans.lproj/Localizable.strings +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/zh-Hans.lproj/Localizable.strings @@ -278,9 +278,7 @@ "GroupMemberInfo" = "成员信息"; -"GroupAbout" = "About"; - -"GroupAboutChannel" = "About"; +"GroupDescription" = "Description"; "GroupMemberMakeAdmin" = "设为群组管理员"; diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewController.swift index 65a94f44ff..6ade478417 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewController.swift @@ -66,14 +66,7 @@ public class AAGroupViewController: AAContentTableController { } } - // About - if let about = self.group.about.get() { - if self.group.groupType == ACGroupType.CHANNEL() { - s.text("GroupAboutChannel", content: about) - } else { - s.text("GroupAbout", content: about) - } - } + // if self.group.isCanEditInfo.get().booleanValue() { // @@ -137,6 +130,18 @@ public class AAGroupViewController: AAContentTableController { } + // About + if let about = self.group.about.get() { + section { (s) in + + s.autoSeparatorTopOffset = 1 + + s.header(AALocalized("GroupDescription").uppercaseString).height = 22 + + s.text(nil, content: about) + } + } + // Calls if (ActorSDK.sharedActor().enableCalls) { let members = (group.members.get() as! JavaUtilHashSet).size() diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/ManagedCells.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/ManagedCells.swift index 5f3c160e5f..fae46de942 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/ManagedCells.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/ManagedCells.swift @@ -172,7 +172,7 @@ public class AATextRow: AAManagedRow { // Cell public override func rangeCellHeightForItem(table: AAManagedTable, indexPath: AARangeIndexPath) -> CGFloat { - return AATextCell.measure(content!, width: table.tableView.width, enableNavigation: navigate) + return AATextCell.measure(title, text: content!, width: table.tableView.width, enableNavigation: navigate) } public override func rangeCellForItem(table: AAManagedTable, indexPath: AARangeIndexPath) -> UITableViewCell { @@ -222,7 +222,7 @@ public extension AAManagedSection { return r } - public func text(title: String, @noescape closure: (r: AATextRow) -> ()) -> AATextRow { + public func text(title: String?, @noescape closure: (r: AATextRow) -> ()) -> AATextRow { let r = text() r.title = AALocalized(title) closure(r: r) @@ -231,13 +231,21 @@ public extension AAManagedSection { return r } - public func text(title: String, content: String) -> AATextRow { + public func text(title: String?, content: String) -> AATextRow { let r = text() r.title = AALocalized(title) r.content = content r.initTable(self.table) return r } + + public func text(content: String) -> AATextRow { + let r = text() + r.title = nil + r.content = content + r.initTable(self.table) + return r + } } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/Cells/AATextCell.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/Cells/AATextCell.swift index 57cac77545..e23cf19ba4 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/Cells/AATextCell.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/Cells/AATextCell.swift @@ -33,7 +33,9 @@ public class AATextCell: AATableViewCell { public func setContent(title: String?, content: String?, isAction: Bool) { titleLabel.text = title + titleLabel.hidden = title == nil contentLabel.text = content + if isAction { contentLabel.textColor = appStyle.cellTintColor } else { @@ -44,13 +46,22 @@ public class AATextCell: AATableViewCell { public override func layoutSubviews() { super.layoutSubviews() - titleLabel.frame = CGRect(x: 15, y: 7, width: contentView.bounds.width - 30, height: titleLabel.bounds.height) - contentLabel.frame = CGRect(x: 15, y: 27, width: contentView.bounds.width - 30, height: 10000) + if titleLabel.hidden { + contentLabel.frame = CGRect(x: 15, y: 7, width: contentView.bounds.width - 30, height: 10000) + } else { + titleLabel.frame = CGRect(x: 15, y: 7, width: contentView.bounds.width - 30, height: titleLabel.bounds.height) + contentLabel.frame = CGRect(x: 15, y: 27, width: contentView.bounds.width - 30, height: 10000) + } contentLabel.sizeToFit() } - public class func measure(text: String, width: CGFloat, enableNavigation: Bool) -> CGFloat { + public class func measure(title: String?, text: String, width: CGFloat, enableNavigation: Bool) -> CGFloat { let size = UIViewMeasure.measureText(text, width: width - 30 - (enableNavigation ? 30 : 0), fontSize: 17) - return CGFloat(size.height + 36) + + if title != nil { + return CGFloat(size.height + 36) + } else { + return CGFloat(size.height + 16) + } } } \ No newline at end of file diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Group.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Group.java index a50e9bea3c..e6e40e1bf7 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Group.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Group.java @@ -265,6 +265,7 @@ public Group editTitle(String title) { w.isHidden(), w.getGroupType(), w.canSendMessage(), + w.isDeleted(), w.getExt()); res.setUnmappedObjects(w.getUnmappedObjects()); return new Group(res, getWrappedExt()); @@ -282,6 +283,7 @@ public Group editAvatar(ApiAvatar avatar) { w.isHidden(), w.getGroupType(), w.canSendMessage(), + w.isDeleted(), w.getExt()); res.setUnmappedObjects(w.getUnmappedObjects()); return new Group(res, getWrappedExt()); @@ -299,6 +301,7 @@ public Group editIsMember(boolean isMember) { w.isHidden(), w.getGroupType(), w.canSendMessage(), + w.isDeleted(), w.getExt()); res.setUnmappedObjects(w.getUnmappedObjects()); return new Group(res, getWrappedExt()); @@ -316,6 +319,7 @@ public Group editExt(ApiMapValue ext) { w.isHidden(), w.getGroupType(), w.canSendMessage(), + w.isDeleted(), ext); res.setUnmappedObjects(w.getUnmappedObjects()); return new Group(res, getWrappedExt()); @@ -333,6 +337,7 @@ public Group editCanWrite(boolean canWrite) { w.isHidden(), w.getGroupType(), canWrite, + w.isDeleted(), w.getExt()); res.setUnmappedObjects(w.getUnmappedObjects()); return new Group(res, getWrappedExt()); @@ -380,6 +385,7 @@ public Group editMembers(List members) { w.isHidden(), w.getGroupType(), w.canSendMessage(), + w.isDeleted(), w.getExt()); return new Group(res, fullExt); @@ -447,6 +453,7 @@ public Group editMembers(List added, List removed, int count w.isHidden(), w.getGroupType(), w.canSendMessage(), + w.isDeleted(), w.getExt()); return new Group(res, fullExt); @@ -464,6 +471,7 @@ public Group editMembersCount(int membersCount) { w.isHidden(), w.getGroupType(), w.canSendMessage(), + w.isDeleted(), w.getExt()); res.setUnmappedObjects(w.getUnmappedObjects()); return new Group(res, getWrappedExt()); From 776cc5cc7b081d4a55afe836e06b2ebb735a078f Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Thu, 21 Jul 2016 15:49:48 +0300 Subject: [PATCH 081/414] feat(iOS): New Group Info Edit --- .../Sources/ActorCore/ActorCoreExt.swift | 6 +- .../Group/AAGroupEditInfoViewController.swift | 107 ++++++++++++++++-- .../Group/AAGroupViewController.swift | 2 +- .../Controllers/Managed Runtime/Alerts.swift | 6 +- .../Sources/Views/Cells/AATextCell.swift | 2 +- 5 files changed, 108 insertions(+), 15 deletions(-) diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/ActorCoreExt.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/ActorCoreExt.swift index cc5203531a..fe8ee59a22 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/ActorCoreExt.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/ActorCoreExt.swift @@ -103,8 +103,10 @@ public extension ACCocoaMessenger { changeMyAvatarWithDescriptor(prepareAvatar(image)) } - public func changeGroupAvatar(gid: jint, image: UIImage) { - changeGroupAvatarWithGid(gid, withDescriptor: prepareAvatar(image)) + public func changeGroupAvatar(gid: jint, image: UIImage) -> String { + let fileName = prepareAvatar(image) + changeGroupAvatarWithGid(gid, withDescriptor: fileName) + return fileName } public func requestFileState(fileId: jlong, notDownloaded: (()->())?, onDownloading: ((progress: Double) -> ())?, onDownloaded: ((reference: String) -> ())?) { diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupEditInfoViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupEditInfoViewController.swift index b92beff15e..83a2766962 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupEditInfoViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupEditInfoViewController.swift @@ -3,16 +3,20 @@ // import Foundation +import SZTextView -public class AAGroupEditInfoController: AAViewController { +public class AAGroupEditInfoController: AAViewController, UITextViewDelegate { private let scrollView = UIScrollView() private let bgContainer = UIView() private let topSeparator = UIView() private let bottomSeparator = UIView() + private let avatarView = AAAvatarView() private let nameInput = UITextField() - private let separator = UIView() + private let nameInputSeparator = UIView() + private let descriptionView = SZTextView() + private let descriptionSeparator = UIView() public init(gid: Int) { super.init() @@ -30,28 +34,115 @@ public class AAGroupEditInfoController: AAViewController { scrollView.alwaysBounceVertical = true - separator.backgroundColor = appStyle.vcSeparatorColor + nameInputSeparator.backgroundColor = appStyle.vcSeparatorColor topSeparator.backgroundColor = appStyle.vcSeparatorColor bottomSeparator.backgroundColor = appStyle.vcSeparatorColor + descriptionSeparator.backgroundColor = appStyle.vcSeparatorColor bgContainer.backgroundColor = appStyle.vcBgColor scrollView.addSubview(bgContainer) + bgContainer.addSubview(avatarView) bgContainer.addSubview(nameInput) - bgContainer.addSubview(separator) + bgContainer.addSubview(nameInputSeparator) + bgContainer.addSubview(descriptionView) + bgContainer.addSubview(descriptionSeparator) bgContainer.addSubview(topSeparator) bgContainer.addSubview(bottomSeparator) view.addSubview(scrollView) + + avatarView.bind(group.name.get(), id: gid, avatar: group.avatar.get()) + avatarView.viewDidTap = { + self.avatarDidTap() + } + + nameInput.font = UIFont.systemFontOfSize(16) + nameInput.placeholder = "Name" + nameInput.text = group.name.get() + + descriptionView.delegate = self + descriptionView.font = UIFont.systemFontOfSize(16) + descriptionView.placeholder = "Description" + descriptionView.text = group.about.get() + descriptionView.scrollEnabled = false + + navigationItem.title = "Edit Group" + navigationItem.rightBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationSave"), style: .Plain, target: self, action: #selector(saveDidPressed)) } public override func viewWillLayoutSubviews() { super.viewWillLayoutSubviews() scrollView.frame = CGRectMake(0, 0, view.width, view.height) - nameInput.frame = CGRectMake(72, 22, view.width - 72 - 10, 44) - separator.frame = CGRectMake(72, 66, view.width - 72, 0.5) - bgContainer.frame = CGRectMake(0, 0, view.width, 144) - topSeparator.frame = CGRectMake(0, 0, bgContainer.width, 0.5) + + nameInput.frame = CGRectMake(94, 34, view.width - 94 - 10, 24) + nameInputSeparator.frame = CGRectMake(94, 34 + 24, view.width - 94, 0.5) + avatarView.frame = CGRectMake(14, 14, 66, 66) + + descriptionSeparator.frame = CGRectMake(0, 94, view.width, 0.5) + topSeparator.frame = CGRectMake(0, 0, view.width, 0.5) + + layoutContainer() + } + + public func avatarDidTap() { + let hasCamera = UIImagePickerController.isSourceTypeAvailable(UIImagePickerControllerSourceType.Camera) + self.showActionSheet( hasCamera ? ["PhotoCamera", "PhotoLibrary"] : ["PhotoLibrary"], + cancelButton: "AlertCancel", + destructButton: self.group.getAvatarModel().get() != nil ? "PhotoRemove" : nil, + sourceView: self.view, + sourceRect: self.view.bounds, + tapClosure: { (index) -> () in + if (index == -2) { + self.confirmAlertUser("PhotoRemoveGroupMessage", + action: "PhotoRemove", + tapYes: { () -> () in + Actor.removeGroupAvatarWithGid(jint(self.gid)) + self.avatarView.bind(self.group.name.get(), id: self.gid, avatar: nil) + }, tapNo: nil) + } else if (index >= 0) { + let takePhoto: Bool = (index == 0) && hasCamera + self.pickAvatar(takePhoto, closure: { (image) -> () in + let fileName = Actor.changeGroupAvatar(jint(self.gid), image: image) + self.avatarView.bind(self.group.name.get(), id: self.gid, fileName: CocoaFiles.pathFromDescriptor(fileName)) + }) + } + }) + } + + public func saveDidPressed() { + let text = nameInput.text!.trim() + let about = self.descriptionView.text!.trim() + + if text != group.name.get() { + executePromise(Actor.editGroupTitleWithGid(jint(gid), withTitle: text).then({ (v: ARVoid!) in + if about != self.group.about.get() { + self.executePromise(Actor.editGroupAboutWithGid(jint(self.gid), withAbout: about).then({ (v: ARVoid!) in + self.navigateBack() + })) + } else { + self.navigateBack() + } + })) + } else { + if about != self.group.about.get() { + self.executePromise(Actor.editGroupAboutWithGid(jint(self.gid), withAbout: about).then({ (v: ARVoid!) in + self.navigateBack() + })) + } else { + self.navigateBack() + } + } + } + + public func textViewDidChange(textView: UITextView) { + layoutContainer() + } + + private func layoutContainer() { + let newSize = descriptionView.sizeThatFits(CGSize(width: view.width - 20, height: CGFloat.max)) + descriptionView.frame = CGRectMake(10, 100, view.width - 20, max(newSize.height, 33)) + bgContainer.frame = CGRectMake(0, 0, view.width, 100 + descriptionView.height + 8) bottomSeparator.frame = CGRectMake(0, bgContainer.height - 0.5, bgContainer.width, 0.5) } } \ No newline at end of file diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewController.swift index 6ade478417..7361c30b17 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewController.swift @@ -20,6 +20,7 @@ public class AAGroupViewController: AAContentTableController { self.gid = gid self.autoTrack = true + self.unbindOnDissapear = true self.title = AALocalized("ProfileTitle") } @@ -50,7 +51,6 @@ public class AAGroupViewController: AAContentTableController { r.avatar = self.group.getAvatarModel().get() r.title = self.group.getNameModel().get() r.subtitle = Actor.getFormatter().formatGroupMembers(self.group.getMembersCountModel().get().intValue()) - self.group.getPresenceModel().get() } r.avatarDidTap = { (view) -> () in diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/Alerts.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/Alerts.swift index 0df2a078c6..215e68eaef 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/Alerts.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/Alerts.swift @@ -16,8 +16,8 @@ public extension UIViewController { } public func confirmAlertUser(message: String, action: String, tapYes: ()->(), tapNo: (()->())? = nil) { - let controller = UIAlertController(title: nil, message: message, preferredStyle: UIAlertControllerStyle.Alert) - controller.addAction(UIAlertAction(title: AALocalized(message), style: UIAlertActionStyle.Default, handler: { (alertView) -> () in + let controller = UIAlertController(title: nil, message: AALocalized(message), preferredStyle: UIAlertControllerStyle.Alert) + controller.addAction(UIAlertAction(title: AALocalized(action), style: UIAlertActionStyle.Default, handler: { (alertView) -> () in tapYes() })) controller.addAction(UIAlertAction(title: AALocalized("AlertCancel"), style: UIAlertActionStyle.Cancel, handler: { (alertView) -> () in @@ -48,7 +48,7 @@ public extension UIViewController { if destructButton != nil { controller.addAction(UIAlertAction(title: AALocalized(destructButton), style: UIAlertActionStyle.Destructive, handler: { (alertView) -> () in - tapClosure(index: -1) + tapClosure(index: -2) })) } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/Cells/AATextCell.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/Cells/AATextCell.swift index e23cf19ba4..7986ac6b13 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/Cells/AATextCell.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/Cells/AATextCell.swift @@ -61,7 +61,7 @@ public class AATextCell: AATableViewCell { if title != nil { return CGFloat(size.height + 36) } else { - return CGFloat(size.height + 16) + return CGFloat(size.height + 15) } } } \ No newline at end of file From b9a7763edf8f8602b58f3db24fa8d9b82bdbb318 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Thu, 21 Jul 2016 17:53:59 +0300 Subject: [PATCH 082/414] wip(iOS): Group Type Change --- .../ActorSDK.xcodeproj/project.pbxproj | 6 +- .../Resources/Base.lproj/Localizable.strings | 6 + .../Resources/es.lproj/Localizable.strings | 6 + .../Resources/pt.lproj/Localizable.strings | 6 + .../Resources/ru.lproj/Localizable.strings | 6 + .../zh-Hans.lproj/Localizable.strings | 6 + .../ActorSDK/Sources/ActorSDK.swift | 3 + .../Group/AAGroupTypeController.swift | 99 ++++++++ .../Group/AAGroupViewController.swift | 232 +++++++----------- .../AAContentTableController.swift | 3 +- .../Managed Runtime/ManagedCells.swift | 4 + 11 files changed, 234 insertions(+), 143 deletions(-) create mode 100644 actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupTypeController.swift diff --git a/actor-sdk/sdk-core-ios/ActorSDK.xcodeproj/project.pbxproj b/actor-sdk/sdk-core-ios/ActorSDK.xcodeproj/project.pbxproj index ca90813ac2..c58989d635 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK.xcodeproj/project.pbxproj +++ b/actor-sdk/sdk-core-ios/ActorSDK.xcodeproj/project.pbxproj @@ -236,6 +236,7 @@ 06ABFE331D3FAF800031A0D6 /* GCDAsyncSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 06ABFE2F1D3FAF800031A0D6 /* GCDAsyncSocket.h */; settings = {ATTRIBUTES = (Public, ); }; }; 06ABFE341D3FAF800031A0D6 /* GCDAsyncSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 06ABFE301D3FAF800031A0D6 /* GCDAsyncSocket.m */; }; 06ABFE381D3FCCE30031A0D6 /* AAGroupEditInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06ABFE371D3FCCE30031A0D6 /* AAGroupEditInfoViewController.swift */; }; + 06ABFE3A1D410D2D0031A0D6 /* AAGroupTypeController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06ABFE391D410D2D0031A0D6 /* AAGroupTypeController.swift */; }; 06B489ED1C9F6EBD0054245B /* AAStickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06B489EC1C9F6EBC0054245B /* AAStickerView.swift */; }; 06C1D0771C8BC9FC00B73632 /* AAAuthNameViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06C1D0761C8BC9FC00B73632 /* AAAuthNameViewController.swift */; }; 06C1D07B1C8BFE5C00B73632 /* AAAuthPhoneViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06C1D07A1C8BFE5C00B73632 /* AAAuthPhoneViewController.swift */; }; @@ -611,6 +612,7 @@ 06ABFE2F1D3FAF800031A0D6 /* GCDAsyncSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDAsyncSocket.h; sourceTree = ""; }; 06ABFE301D3FAF800031A0D6 /* GCDAsyncSocket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDAsyncSocket.m; sourceTree = ""; }; 06ABFE371D3FCCE30031A0D6 /* AAGroupEditInfoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AAGroupEditInfoViewController.swift; sourceTree = ""; }; + 06ABFE391D410D2D0031A0D6 /* AAGroupTypeController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AAGroupTypeController.swift; sourceTree = ""; }; 06B489EC1C9F6EBC0054245B /* AAStickerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AAStickerView.swift; sourceTree = ""; }; 06C1D0761C8BC9FC00B73632 /* AAAuthNameViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AAAuthNameViewController.swift; sourceTree = ""; }; 06C1D07A1C8BFE5C00B73632 /* AAAuthPhoneViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AAAuthPhoneViewController.swift; sourceTree = ""; }; @@ -1234,9 +1236,10 @@ isa = PBXGroup; children = ( 066A52E81BC52A25000E606E /* Cells */, - 066A52E31BC52A20000E606E /* AAAddParticipantViewController.swift */, 066A52E21BC52A20000E606E /* AAGroupViewController.swift */, 06ABFE371D3FCCE30031A0D6 /* AAGroupEditInfoViewController.swift */, + 06ABFE391D410D2D0031A0D6 /* AAGroupTypeController.swift */, + 066A52E31BC52A20000E606E /* AAAddParticipantViewController.swift */, 066A52E41BC52A20000E606E /* AAInviteLinkViewController.swift */, ); path = Group; @@ -2091,6 +2094,7 @@ 061850E31C95CBF000C522D5 /* YYTextKeyboardManager.m in Sources */, 066A52571BC4EF61000E606E /* AACollectionViewController.swift in Sources */, 066A532F1BC53406000E606E /* AABubbleMediaCell.swift in Sources */, + 06ABFE3A1D410D2D0031A0D6 /* AAGroupTypeController.swift in Sources */, 15D35F631C20187E00E3717A /* AAModernConversationAudioPlayer.m in Sources */, 066A527F1BC51ED0000E606E /* AARootSplitViewController.swift in Sources */, 066A52481BC4EED5000E606E /* AAProgressView.swift in Sources */, diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/Base.lproj/Localizable.strings b/actor-sdk/sdk-core-ios/ActorSDK/Resources/Base.lproj/Localizable.strings index 3f6f1ddeb1..7a77f90cc8 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Resources/Base.lproj/Localizable.strings +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/Base.lproj/Localizable.strings @@ -294,6 +294,12 @@ "GroupDescription" = "Description"; +"GroupAdministration" = "Administration"; + +"GroupTypeTitle" = "Group Type"; + +"GroupTypeTitleChannel" = "Channel Type"; + "GroupMemberAdmin" = "admin"; "GroupMemberInfo" = "Profile"; diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/es.lproj/Localizable.strings b/actor-sdk/sdk-core-ios/ActorSDK/Resources/es.lproj/Localizable.strings index 104d10d549..286771cab2 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Resources/es.lproj/Localizable.strings +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/es.lproj/Localizable.strings @@ -291,6 +291,12 @@ "GroupDescription" = "Description"; +"GroupAdministration" = "Administration"; + +"GroupTypeTitle" = "Group Type"; + +"GroupTypeTitleChannel" = "Channel Type"; + "GroupMemberAdmin" = "admin"; "GroupMemberInfo" = "Perfil"; diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/pt.lproj/Localizable.strings b/actor-sdk/sdk-core-ios/ActorSDK/Resources/pt.lproj/Localizable.strings index fed6108fc9..55547b8930 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Resources/pt.lproj/Localizable.strings +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/pt.lproj/Localizable.strings @@ -280,6 +280,12 @@ "GroupDescription" = "Description"; +"GroupAdministration" = "Administration"; + +"GroupTypeTitle" = "Group Type"; + +"GroupTypeTitleChannel" = "Channel Type"; + "GroupViewMembers" = "Membros"; "GroupMemberAdmin" = "admin"; diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/ru.lproj/Localizable.strings b/actor-sdk/sdk-core-ios/ActorSDK/Resources/ru.lproj/Localizable.strings index e9f1dd2f9c..163d7f2de1 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Resources/ru.lproj/Localizable.strings +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/ru.lproj/Localizable.strings @@ -292,6 +292,12 @@ "GroupDescription" = "Описание"; +"GroupAdministration" = "Администрирование"; + +"GroupTypeTitle" = "Тип группы"; + +"GroupTypeTitleChannel" = "Тип канала"; + "GroupViewMembers" = "Участники"; "GroupMemberAdmin" = "админ."; diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/zh-Hans.lproj/Localizable.strings b/actor-sdk/sdk-core-ios/ActorSDK/Resources/zh-Hans.lproj/Localizable.strings index 828ba280c5..d113b29052 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Resources/zh-Hans.lproj/Localizable.strings +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/zh-Hans.lproj/Localizable.strings @@ -280,6 +280,12 @@ "GroupDescription" = "Description"; +"GroupAdministration" = "Administration"; + +"GroupTypeTitle" = "Group Type"; + +"GroupTypeTitleChannel" = "Channel Type"; + "GroupMemberMakeAdmin" = "设为群组管理员"; "GroupMemberMakeMessage" = "确定将 {name} 设为群组管理员吗?"; diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorSDK.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorSDK.swift index 21f8fb0ecb..427782ab61 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorSDK.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorSDK.swift @@ -80,6 +80,9 @@ import DZNWebViewController /// Invitation URL for apps public var inviteUrl: String = "https://actor.im/dl" + + /// Invitation URL for apps + public var invitePrefix: String? = "https://actor.im/join/" /// Privacy Policy URL public var privacyPolicyUrl: String? = nil diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupTypeController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupTypeController.swift new file mode 100644 index 0000000000..ddda9e6e4d --- /dev/null +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupTypeController.swift @@ -0,0 +1,99 @@ +// +// Copyright (c) 2014-2016 Actor LLC. +// + +import Foundation + +public class AAGroupTypeViewController: AAContentTableController { + + private var isChannel: Bool = false + private var isPublic: Bool = false + private var linkSection: AAManagedSection! + private var publicRow: AACommonRow! + private var privateRow: AACommonRow! + + public init(gid: Int) { + super.init(style: .SettingsGrouped) + self.gid = gid + self.isChannel = group.groupType == ACGroupType.CHANNEL() + if (isChannel) { + navigationItem.title = AALocalized("GroupTypeTitleChannel") + } else { + navigationItem.title = AALocalized("GroupTypeTitle") + } + navigationItem.rightBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationSave"), style: .Plain, target: self, action: #selector(saveDidTap)) + } + + public required init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public override func tableDidLoad() { + + section { (s) in + + s.headerText = "Group Type".uppercaseString + if self.isPublic { + s.footerText = "Public groups can be found in search and anyone can joing" + } else { + s.footerText = "Private groups can be joined only via personal invitation" + } + self.publicRow = s.common({ (r) in + r.content = "Public Group" + r.selectAction = { () -> Bool in + if !self.isPublic { + self.isPublic = true + self.publicRow.rebind() + self.privateRow.rebind() + s.footerText = "Public groups can be found in search and anyone can joing" + self.tableView.reloadSection(0, withRowAnimation: .Automatic) + self.managedTable.sections.append(self.linkSection) + self.tableView.insertSection(1, withRowAnimation: .Fade) + } + return true + } + r.bindAction = { (r) in + if self.isPublic { + r.style = .Checkmark + } else { + r.style = .Normal + } + } + }) + + self.privateRow = s.common({ (r) in + r.content = "Private Group" + r.selectAction = { () -> Bool in + if self.isPublic { + self.isPublic = false + self.publicRow.rebind() + self.privateRow.rebind() + s.footerText = "Private groups can be joined only via personal invitation" + self.tableView.reloadSection(0, withRowAnimation: .Automatic) + self.managedTable.sections.removeAtIndex(1) + self.tableView.deleteSection(1, withRowAnimation: .Fade) + } + return true + } + r.bindAction = { (r) in + if !self.isPublic { + r.style = .Checkmark + } else { + r.style = .Normal + } + } + }) + } + + self.linkSection = section { (s) in + s.headerText = "Hey!" + } + if !self.isPublic { + managedTable.sections.removeAtIndex(1) + } + } + + public func saveDidTap() { + + } +} \ No newline at end of file diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewController.swift index 7361c30b17..719eef751b 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewController.swift @@ -41,8 +41,6 @@ public class AAGroupViewController: AAContentTableController { // Header section { (s) -> () in - - // Header: Avatar self.headerRow = s.avatar { (r) -> () in r.id = self.gid @@ -65,69 +63,6 @@ public class AAGroupViewController: AAContentTableController { } } } - - - -// if self.group.isCanEditInfo.get().booleanValue() { -// -// // Header: Change photo -// s.action("GroupSetPhoto") { (r) -> () in -// r.selectAction = { () -> Bool in -// let hasCamera = UIImagePickerController.isSourceTypeAvailable(UIImagePickerControllerSourceType.Camera) -// self.showActionSheet( hasCamera ? ["PhotoCamera", "PhotoLibrary"] : ["PhotoLibrary"], -// cancelButton: "AlertCancel", -// destructButton: self.group.getAvatarModel().get() != nil ? "PhotoRemove" : nil, -// sourceView: self.view, -// sourceRect: self.view.bounds, -// tapClosure: { (index) -> () in -// if (index == -2) { -// self.confirmAlertUser("PhotoRemoveGroupMessage", -// action: "PhotoRemove", -// tapYes: { () -> () in -// Actor.removeGroupAvatarWithGid(jint(self.gid)) -// }, tapNo: nil) -// } else if (index >= 0) { -// let takePhoto: Bool = (index == 0) && hasCamera -// self.pickAvatar(takePhoto, closure: { (image) -> () in -// Actor.changeGroupAvatar(jint(self.gid), image: image) -// }) -// } -// }) -// -// return true -// } -// } -// -// // Header: Change title -// s.action("GroupSetTitle") { (r) -> () in -// r.selectAction = { () -> Bool in -// self.startEditField { (c) -> () in -// -// c.title = "GroupEditHeader" -// -// c.fieldHint = "GroupEditHint" -// -// c.actionTitle = "NavigationSave" -// -// c.initialText = self.group.getNameModel().get() -// -// c.didDoneTap = { (t, c) -> () in -// -// if t.length == 0 { -// return -// } -// -// // c.executeSafeOnlySuccess(Actor.editGroupTitleCommandWithGid(jint(self.gid), withTitle: t)!, successBlock: { (val) -> Void in -// // c.dismiss() -// // }) -// } -// } -// -// return true -// } -// } -// } - } // About @@ -142,6 +77,13 @@ public class AAGroupViewController: AAContentTableController { } } + // Link + if let shortName = self.group.shortName.get(), let prefix = ActorSDK.sharedActor().invitePrefix { + section { (s) in + s.text(nil, content: prefix + shortName) + } + } + // Calls if (ActorSDK.sharedActor().enableCalls) { let members = (group.members.get() as! JavaUtilHashSet).size() @@ -159,13 +101,14 @@ public class AAGroupViewController: AAContentTableController { } } - // Notifications + // Actions and Settings section { (s) -> () in - let groupPeer: ACPeer! = ACPeer.groupWithInt(jint(self.gid)) - + // Notifications s.common { (r) -> () in + let groupPeer: ACPeer! = ACPeer.groupWithInt(jint(self.gid)) + r.style = .Switch r.content = AALocalized("GroupNotifications") @@ -200,66 +143,44 @@ public class AAGroupViewController: AAContentTableController { } } - // View Members - if self.group.isCanViewMembers.get().booleanValue() && self.group.isAsyncMembers.get().booleanValue() { - //section { (s) -> () in - - s.common({ (r) -> () in - r.content = AALocalized("GroupViewMembers") - r.style = .Normal - r.selectAction = { () -> Bool in - // TODO: Implement - return false - } - }) - - s.action("GroupAddParticipant") { (r) -> () in - - r.selectAction = { () -> Bool in - let addParticipantController = AAAddParticipantViewController(gid: self.gid) - let navigationController = AANavigationController(rootViewController: addParticipantController) - if (AADevice.isiPad) { - navigationController.modalInPopover = true - navigationController.modalPresentationStyle = UIModalPresentationStyle.CurrentContext - } - self.presentViewController(navigationController, animated: true, completion: nil) - return false - } + // Admininstration + if group.isCanEditShortName.get().booleanValue() || group.isCanDelete.get().booleanValue() { + s.common({ (r) in + r.content = AALocalized("GroupAdministration") + r.selectAction = { () -> Bool in + self.navigateNext(AAGroupTypeViewController(gid: self.gid)) + return false } - //} + }) } - - } - - - // Leave group - if group.isCanLeave.get().booleanValue() { - section { (s) -> () in + + // View Members + if self.group.isCanViewMembers.get().booleanValue() && self.group.isAsyncMembers.get().booleanValue() { s.common({ (r) -> () in - - if self.group.groupType == ACGroupType.CHANNEL() { - r.content = AALocalized("GroupLeaveChannel") - } else { - r.content = AALocalized("GroupLeave") + r.content = AALocalized("GroupViewMembers") + r.style = .Normal + r.selectAction = { () -> Bool in + // TODO: Implement + return false } + }) + + s.action("GroupAddParticipant") { (r) -> () in - r.style = .Destructive r.selectAction = { () -> Bool in - - let title: String - if self.group.groupType == ACGroupType.CHANNEL() { - title = AALocalized("GroupLeaveConfirmChannel") - } else { - title = AALocalized("GroupLeaveConfirm") + let addParticipantController = AAAddParticipantViewController(gid: self.gid) + let navigationController = AANavigationController(rootViewController: addParticipantController) + if (AADevice.isiPad) { + navigationController.modalInPopover = true + navigationController.modalPresentationStyle = UIModalPresentationStyle.CurrentContext } - self.confirmDestructive(title, action: AALocalized("GroupLeaveConfirmAction"), yes: { () -> () in - // self.executeSafe(Actor.leaveGroupCommandWithGid(jint(self.gid))!) - }) + self.presentViewController(navigationController, animated: true, completion: nil) + return false - return true } - }) + } } + } // Members @@ -312,8 +233,6 @@ public class AAGroupViewController: AAContentTableController { return true } - let name = user.getNameModel().get() - self.alertSheet { (a: AAAlertSetting) -> () in a.cancel = "AlertCancel" @@ -362,17 +281,18 @@ public class AAGroupViewController: AAContentTableController { }) } }) + + // Detect if we are admin + let members: [ACGroupMember] = self.group.members.get().toArray().toSwiftArray() + var isAdmin = self.group.ownerId.get()?.intValue() == Actor.myUid() + if !isAdmin { + for m in members { + if m.uid == Actor.myUid() { + isAdmin = m.isAdministrator + } + } + } - // // Detect if we are admin - // let members: [ACGroupMember] = self.group.members.get().toArray().toSwiftArray() - // var isAdmin = self.group.creatorId == Actor.myUid() - // if !isAdmin { - // for m in members { - // if m.uid == Actor.myUid() { - // isAdmin = m.isAdministrator - // } - // } - // } // // // Can mark as admin // let canMarkAdmin = isAdmin && !d.isAdministrator @@ -388,17 +308,17 @@ public class AAGroupViewController: AAContentTableController { // } // Can kick user - // let canKick = isAdmin || d.inviterUid == Actor.myUid() - // - // if canKick { - // a.destructive("GroupMemberKick") { () -> () in - // self.confirmDestructive(AALocalized("GroupMemberKickMessage") - // .replace("{name}", dest: name), action: AALocalized("GroupMemberKickAction")) { - // - // self.executeSafe(Actor.kickMemberCommandWithGid(jint(self.gid), withUid: user.getId())!) - // } - // } - // } + let canKick = isAdmin || d.inviterUid == Actor.myUid() + let name = Actor.getUserWithUid(d.uid).getNameModel().get() + if canKick { + a.destructive("GroupMemberKick") { () -> () in + self.confirmDestructive(AALocalized("GroupMemberKickMessage") + .replace("{name}", dest: name), action: AALocalized("GroupMemberKickAction")) { + +// self.executeSafe(Actor.kickMemberCommandWithGid(jint(self.gid), withUid: user.getId())!) + } + } + } } return true @@ -406,6 +326,36 @@ public class AAGroupViewController: AAContentTableController { } } } + + // Leave group + if group.isCanLeave.get().booleanValue() { + section { (s) -> () in + s.common({ (r) -> () in + + if self.group.groupType == ACGroupType.CHANNEL() { + r.content = AALocalized("GroupLeaveChannel") + } else { + r.content = AALocalized("GroupLeave") + } + + r.style = .Destructive + r.selectAction = { () -> Bool in + + let title: String + if self.group.groupType == ACGroupType.CHANNEL() { + title = AALocalized("GroupLeaveConfirmChannel") + } else { + title = AALocalized("GroupLeaveConfirm") + } + self.confirmDestructive(title, action: AALocalized("GroupLeaveConfirmAction"), yes: { () -> () in + // self.executeSafe(Actor.leaveGroupCommandWithGid(jint(self.gid))!) + }) + + return true + } + }) + } + } } public override func tableWillBind(binder: AABinder) { diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/AAContentTableController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/AAContentTableController.swift index 6849f816d7..8ec10f42dd 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/AAContentTableController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/AAContentTableController.swift @@ -33,7 +33,7 @@ public class AAContentTableController: AAManagedTableController, AAManagedTableC // DSL Implementation - public func section(@noescape closure: (s: AAManagedSection) -> ()) { + public func section(@noescape closure: (s: AAManagedSection) -> ()) -> AAManagedSection { if !isInLoad { fatalError("Unable to change sections not during tableDidLoad method call") } @@ -48,6 +48,7 @@ public class AAContentTableController: AAManagedTableController, AAManagedTableC } } closure(s: s) + return s } public func search(cell: C.Type, @noescape closure: (s: AAManagedSearchConfig) -> ()) { diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/ManagedCells.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/ManagedCells.swift index fae46de942..56a2bf38fd 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/ManagedCells.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/ManagedCells.swift @@ -354,6 +354,10 @@ public class AACommonRow: AAManagedRow { } } } + + public func rebind() { + bindAction?(r: self) + } } public extension AAManagedSection { From dbf77fb57010b340856807eb044cd2c6cb7f9052 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Thu, 21 Jul 2016 18:01:15 +0300 Subject: [PATCH 083/414] fix(core): Fixing DialogsLoaded state --- .../core/modules/messaging/history/DialogsHistoryActor.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/history/DialogsHistoryActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/history/DialogsHistoryActor.java index 1226b01337..555a81c3fe 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/history/DialogsHistoryActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/history/DialogsHistoryActor.java @@ -44,6 +44,8 @@ public void preStart() { historyLoaded = preferences().getBool(KEY_LOADED, false); if (!preferences().getBool(KEY_LOADED_INIT, false)) { self().send(new LoadMore()); + } else { + context().getAppStateModule().onDialogsLoaded(); } } @@ -88,7 +90,6 @@ private void onLoadedMore(List rawDialogs) { } }); } else { - context().getAppStateModule().onDialogsLoaded(); markAsLoaded(); } } @@ -98,6 +99,7 @@ private void markAsLoaded() { historyLoaded = true; preferences().putBool(KEY_LOADED, true); preferences().putBool(KEY_LOADED_INIT, true); + context().getAppStateModule().onDialogsLoaded(); } private void markAsSliceLoaded(long date) { @@ -107,6 +109,7 @@ private void markAsSliceLoaded(long date) { preferences().putBool(KEY_LOADED, false); preferences().putBool(KEY_LOADED_INIT, true); preferences().putLong(KEY_LOADED_DATE, date); + context().getAppStateModule().onDialogsLoaded(); } // From 2163fbf12680f0630eefc80b3ead57541ab4da7c Mon Sep 17 00:00:00 2001 From: kor_ka Date: Thu, 21 Jul 2016 01:17:46 +0300 Subject: [PATCH 084/414] fix(android): group info --- .../java/im/actor/sdk/controllers/group/GroupInfoFragment.java | 1 + .../src/main/java/im/actor/core/viewmodel/GroupVM.java | 1 + 2 files changed, 2 insertions(+) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java index e926dbeffa..17182b093b 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java @@ -239,6 +239,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa header.findViewById(R.id.after_settings_divider).setVisibility(View.VISIBLE); } } else { + members.setVisibility(View.GONE); header.findViewById(R.id.after_settings_divider).setVisibility(View.GONE); } }); diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java index 91c9a4de22..1a76dadb54 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java @@ -465,6 +465,7 @@ protected void updateValues(@NotNull Group rawObj) { isChanged |= isCanViewMembers.change(rawObj.isCanViewMembers()); isChanged |= isCanInviteMembers.change(rawObj.isCanInviteMembers()); isChanged |= isCanEditInfo.change(rawObj.isCanEditInfo()); + isChanged |= isCanEditShortName.change(rawObj.isCanEditShortName()); isChanged |= shortName.change(rawObj.getShortName()); isChanged |= isAsyncMembers.change(rawObj.isAsyncMembers()); isChanged |= isHistoryShared.change(rawObj.isSharedHistory()); From d022dbd1be6d6c06b3eb2497c1ad482f0284938b Mon Sep 17 00:00:00 2001 From: kor_ka Date: Thu, 21 Jul 2016 18:09:34 +0300 Subject: [PATCH 085/414] feat(android): members fragment load all members --- .../controllers/group/GroupInfoFragment.java | 2 +- .../controllers/group/MembersFragment.java | 31 ++------- .../group/view/MembersAdapter.java | 66 ++++++++++++++++--- .../src/main/res/layout/fragment_members.xml | 2 +- .../actor/core/entity/PeerSearchEntity.java | 47 ++----------- .../core/modules/search/SearchModule.java | 4 +- 6 files changed, 69 insertions(+), 83 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java index 17182b093b..9cedd34e4b 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java @@ -275,7 +275,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa // Members // - groupUserAdapter = new MembersAdapter(getActivity()); + groupUserAdapter = new MembersAdapter(getActivity(), getArguments().getInt("groupId")); bind(groupVM.getIsAsyncMembers(), groupVM.getMembers(), (isAsyncMembers, valueModel, memberList, valueModel2) -> { if (isAsyncMembers) { groupUserAdapter.setMembers(new ArrayList<>()); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/MembersFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/MembersFragment.java index 8e47223be3..f767550f98 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/MembersFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/MembersFragment.java @@ -13,6 +13,7 @@ import im.actor.sdk.R; import im.actor.sdk.controllers.BaseFragment; import im.actor.sdk.controllers.group.view.MembersAdapter; +import im.actor.sdk.view.adapters.RecyclerListView; import static im.actor.sdk.util.ActorSDKMessenger.messenger; @@ -26,12 +27,6 @@ public static MembersFragment create(int groupId) { return res; } - private static final int LIMIT = 20; - - private int groupId; - private boolean isInitiallyLoaded; - private byte[] nextMembers; - private ArrayList members = new ArrayList<>(); private MembersAdapter adapter; public MembersFragment() { @@ -41,20 +36,13 @@ public MembersFragment() { setShowHome(true); } - @Override - public void onCreate(Bundle saveInstance) { - super.onCreate(saveInstance); - - groupId = getArguments().getInt("groupId"); - } - @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View res = inflater.inflate(R.layout.fragment_members, container, false); - adapter = new MembersAdapter(getContext()); + adapter = new MembersAdapter(getContext(), getArguments().getInt("groupId")); - ((ListView) res.findViewById(R.id.items)).setAdapter(adapter); + ((RecyclerListView) res.findViewById(R.id.items)).setAdapter(adapter); return res; } @@ -62,17 +50,6 @@ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Override public void onResume() { super.onResume(); - if (!isInitiallyLoaded) { - wrap(messenger().loadMembers(groupId, LIMIT, nextMembers)).then(groupMembersSlice -> { - isInitiallyLoaded = true; - members.addAll(groupMembersSlice.getUids()); - nextMembers = groupMembersSlice.getNext(); - ArrayList nMembers = new ArrayList<>(); - for (Integer uid : members) { - nMembers.add(new GroupMember(uid, 0, 0, false)); - } - adapter.setMembers(nMembers, false); - }); - } + adapter.initLoad(); } } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/view/MembersAdapter.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/view/MembersAdapter.java index 66976514a2..013996bbba 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/view/MembersAdapter.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/view/MembersAdapter.java @@ -6,9 +6,9 @@ import android.view.ViewGroup; import android.widget.TextView; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.Comparator; import im.actor.sdk.ActorSDK; import im.actor.sdk.R; @@ -20,15 +20,21 @@ import im.actor.core.entity.GroupMember; import im.actor.core.viewmodel.UserVM; +import static im.actor.sdk.util.ActorSDKMessenger.messenger; import static im.actor.sdk.util.ActorSDKMessenger.users; public class MembersAdapter extends HolderAdapter { - private GroupMember[] members = new GroupMember[0]; + public static final int LOAD_GAP = 10; + private static final int LIMIT = 20; + private ArrayList members = new ArrayList(); private ActorBinder BINDER = new ActorBinder(); + private boolean loadInProgress = false; + private boolean loaddedToEnd = false; - public MembersAdapter(Context context) { + public MembersAdapter(Context context, int groupId) { super(context); + this.groupId = groupId; } public void setMembers(Collection members) { @@ -36,9 +42,9 @@ public void setMembers(Collection members) { } public void setMembers(Collection members, boolean sort) { - this.members = members.toArray(new GroupMember[members.size()]); if (sort) { - Arrays.sort(this.members, (a, b) -> { + GroupMember[] membersArray = members.toArray(new GroupMember[members.size()]); + Arrays.sort(membersArray, (a, b) -> { if (a.isAdministrator() && !b.isAdministrator()) { return -1; } @@ -49,23 +55,65 @@ public void setMembers(Collection members, boolean sort) { String bn = users().get(b.getInviterUid()).getName().get(); return an.compareTo(bn); }); + this.members.addAll(Arrays.asList(membersArray)); + } else { + this.members.addAll(members); + } + notifyDataSetChanged(); + } + + @Override + protected void onBindViewHolder(ViewHolder holder, GroupMember obj, int position, Context context) { + super.onBindViewHolder(holder, obj, position, context); + if (position >= getCount() - LOAD_GAP) { + loadMore(); + } + + } + + private int groupId; + private boolean isInitiallyLoaded; + private byte[] nextMembers; + private ArrayList rawMembers = new ArrayList<>(); + + public void initLoad() { + if (!isInitiallyLoaded) { + loadMore(); + } + } + + private void loadMore() { + if (!loadInProgress && !loaddedToEnd) { + loadInProgress = true; + messenger().loadMembers(groupId, LIMIT, nextMembers).then(groupMembersSlice -> { + isInitiallyLoaded = true; + rawMembers.clear(); + rawMembers.addAll(groupMembersSlice.getUids()); + nextMembers = groupMembersSlice.getNext(); + loaddedToEnd = nextMembers == null; + ArrayList nMembers = new ArrayList<>(); + for (Integer uid : rawMembers) { + nMembers.add(new GroupMember(uid, 0, 0, false)); + } + loadInProgress = false; + setMembers(nMembers, false); + }); } - notifyDataSetInvalidated(); } @Override public int getCount() { - return members.length; + return members.size(); } @Override public GroupMember getItem(int position) { - return members[position]; + return members.get(position); } @Override public long getItemId(int position) { - return members[position].getUid(); + return members.get(position).getUid(); } @Override diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_members.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_members.xml index 745d3b0168..dc3773cbc8 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_members.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_members.xml @@ -3,7 +3,7 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/PeerSearchEntity.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/PeerSearchEntity.java index 53e6fed597..91ab8db3e5 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/PeerSearchEntity.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/PeerSearchEntity.java @@ -3,55 +3,18 @@ public class PeerSearchEntity { private Peer peer; - private String title; - private String description; - private Integer membersCount; - private Long date; - private Integer creatorUid; - private Boolean isPublic; - private Boolean isJoined; + private String optMatchString; - public PeerSearchEntity(Peer peer, String title, String description, Integer membersCount, - Long date, Integer creatorUid, Boolean isPublic, Boolean isJoined) { + public PeerSearchEntity(Peer peer, String optMatchString) { this.peer = peer; - this.title = title; - this.description = description; - this.membersCount = membersCount; - this.date = date; - this.creatorUid = creatorUid; - this.isPublic = isPublic; - this.isJoined = isJoined; + this.optMatchString = optMatchString; } public Peer getPeer() { return peer; } - public String getTitle() { - return title; - } - - public String getDescription() { - return description; - } - - public Integer getMembersCount() { - return membersCount; - } - - public Long getDate() { - return date; - } - - public Integer getCreatorUid() { - return creatorUid; - } - - public Boolean isPublic() { - return isPublic; - } - - public Boolean isJoined() { - return isJoined; + public String getOptMatchString() { + return optMatchString; } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/search/SearchModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/search/SearchModule.java index face3199c0..d0d11c3628 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/search/SearchModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/search/SearchModule.java @@ -119,9 +119,7 @@ public Promise> findPeers(final PeerSearchType type) { responsePeerSearch.getGroups())) .map(responsePeerSearch1 -> ManagedList.of(responsePeerSearch1.getSearchResults()) - .map(r -> new PeerSearchEntity(convert(r.getPeer()), r.getTitle(), - r.getDescription(), r.getMembersCount(), r.getDateCreated(), - r.getCreator(), r.isPublic(), r.isJoined()))); + .map(r -> new PeerSearchEntity(convert(r.getPeer()), r.getOptMatchString()))); } From cb2c3f8564fb832d9e0e826fcc884b4b0798f5ca Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Thu, 21 Jul 2016 19:38:38 +0300 Subject: [PATCH 086/414] feat(iOS): Administration View Controller --- .../ActorSDK.xcodeproj/project.pbxproj | 4 + .../AAGroupAdministrationViewController.swift | 80 +++++++++++++++++++ .../Group/AAGroupEditInfoViewController.swift | 2 +- .../Group/AAGroupTypeController.swift | 29 ++++++- .../Group/AAGroupViewController.swift | 2 +- .../Managed Runtime/ManagedCells.swift | 13 +++ .../Sources/Views/Cells/AACommonCell.swift | 9 ++- .../Sources/Views/Cells/AAEditCell.swift | 13 ++- 8 files changed, 145 insertions(+), 7 deletions(-) create mode 100644 actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupAdministrationViewController.swift diff --git a/actor-sdk/sdk-core-ios/ActorSDK.xcodeproj/project.pbxproj b/actor-sdk/sdk-core-ios/ActorSDK.xcodeproj/project.pbxproj index c58989d635..ae2aefbaa5 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK.xcodeproj/project.pbxproj +++ b/actor-sdk/sdk-core-ios/ActorSDK.xcodeproj/project.pbxproj @@ -237,6 +237,7 @@ 06ABFE341D3FAF800031A0D6 /* GCDAsyncSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 06ABFE301D3FAF800031A0D6 /* GCDAsyncSocket.m */; }; 06ABFE381D3FCCE30031A0D6 /* AAGroupEditInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06ABFE371D3FCCE30031A0D6 /* AAGroupEditInfoViewController.swift */; }; 06ABFE3A1D410D2D0031A0D6 /* AAGroupTypeController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06ABFE391D410D2D0031A0D6 /* AAGroupTypeController.swift */; }; + 06ABFE3D1D41283A0031A0D6 /* AAGroupAdministrationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06ABFE3C1D41283A0031A0D6 /* AAGroupAdministrationViewController.swift */; }; 06B489ED1C9F6EBD0054245B /* AAStickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06B489EC1C9F6EBC0054245B /* AAStickerView.swift */; }; 06C1D0771C8BC9FC00B73632 /* AAAuthNameViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06C1D0761C8BC9FC00B73632 /* AAAuthNameViewController.swift */; }; 06C1D07B1C8BFE5C00B73632 /* AAAuthPhoneViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06C1D07A1C8BFE5C00B73632 /* AAAuthPhoneViewController.swift */; }; @@ -613,6 +614,7 @@ 06ABFE301D3FAF800031A0D6 /* GCDAsyncSocket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDAsyncSocket.m; sourceTree = ""; }; 06ABFE371D3FCCE30031A0D6 /* AAGroupEditInfoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AAGroupEditInfoViewController.swift; sourceTree = ""; }; 06ABFE391D410D2D0031A0D6 /* AAGroupTypeController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AAGroupTypeController.swift; sourceTree = ""; }; + 06ABFE3C1D41283A0031A0D6 /* AAGroupAdministrationViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AAGroupAdministrationViewController.swift; sourceTree = ""; }; 06B489EC1C9F6EBC0054245B /* AAStickerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AAStickerView.swift; sourceTree = ""; }; 06C1D0761C8BC9FC00B73632 /* AAAuthNameViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AAAuthNameViewController.swift; sourceTree = ""; }; 06C1D07A1C8BFE5C00B73632 /* AAAuthPhoneViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AAAuthPhoneViewController.swift; sourceTree = ""; }; @@ -1238,6 +1240,7 @@ 066A52E81BC52A25000E606E /* Cells */, 066A52E21BC52A20000E606E /* AAGroupViewController.swift */, 06ABFE371D3FCCE30031A0D6 /* AAGroupEditInfoViewController.swift */, + 06ABFE3C1D41283A0031A0D6 /* AAGroupAdministrationViewController.swift */, 06ABFE391D410D2D0031A0D6 /* AAGroupTypeController.swift */, 066A52E31BC52A20000E606E /* AAAddParticipantViewController.swift */, 066A52E41BC52A20000E606E /* AAInviteLinkViewController.swift */, @@ -2060,6 +2063,7 @@ 061850DB1C95CBF000C522D5 /* YYTextContainerView.m in Sources */, 15D35F301C20187200E3717A /* info.c in Sources */, 066A532E1BC53406000E606E /* AABubbleBaseFileCell.swift in Sources */, + 06ABFE3D1D41283A0031A0D6 /* AAGroupAdministrationViewController.swift in Sources */, 066A514A1BC4BCE0000E606E /* Dispatch.swift in Sources */, 066A52401BC4EECD000E606E /* AABigPlaceholderView.swift in Sources */, BED5A1F51C48396A0045FDB0 /* NYTPhotosDataSource.m in Sources */, diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupAdministrationViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupAdministrationViewController.swift new file mode 100644 index 0000000000..0f7c13c9ae --- /dev/null +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupAdministrationViewController.swift @@ -0,0 +1,80 @@ +// +// Copyright (c) 2014-2016 Actor LLC. +// + +import Foundation + +public class AAGroupAdministrationViewController: AAContentTableController { + + private var isChannel: Bool = false + private var shortNameRow: AACommonRow! + + public init(gid: Int) { + super.init(style: .SettingsGrouped) + self.gid = gid + self.isChannel = group.groupType == ACGroupType.CHANNEL() + navigationItem.title = AALocalized("GroupAdministration") + } + + public required init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public override func tableDidLoad() { + + section { (s) in + s.footerText = "Control what is possible in this group" + self.shortNameRow = s.common({ (r) in + + if (self.isChannel) { + r.content = AALocalized("GroupTypeTitleChannel") + } else { + r.content = AALocalized("GroupTypeTitle") + } + + r.bindAction = { (r) in + if self.group.shortName.get() != nil { + r.hint = "Public" + } else { + r.hint = "Private" + } + } + + if group.isCanEditShortName.get().booleanValue() { + r.style = .Navigation + r.selectAction = { () -> Bool in + self.navigateNext(AAGroupTypeViewController(gid: self.gid)) + return false + } + } + }) + } + + if group.isCanEditAdministration.get().booleanValue() { + section { (s) in + s.footerText = "All members will see all messages" + s.common({ (r) in + r.content = "Share History" + r.hint = "Shared" + }) + } + } + + if group.isCanDelete.get().booleanValue() { + section { (s) in + s.footerText = "You will lose all messages in this group" + s.danger("Delete Group", closure: { (r) in + + }) + } + } + } + + public override func tableWillBind(binder: AABinder) { + binder.bind(self.group.shortName) { (value: String!) in + if let row = self.shortNameRow { + row.reload() + } + } + } +} \ No newline at end of file diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupEditInfoViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupEditInfoViewController.swift index 83a2766962..5a0354447e 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupEditInfoViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupEditInfoViewController.swift @@ -67,7 +67,7 @@ public class AAGroupEditInfoController: AAViewController, UITextViewDelegate { descriptionView.scrollEnabled = false navigationItem.title = "Edit Group" - navigationItem.rightBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationSave"), style: .Plain, target: self, action: #selector(saveDidPressed)) + navigationItem.rightBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationSave"), style: .Done, target: self, action: #selector(saveDidPressed)) } public override func viewWillLayoutSubviews() { diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupTypeController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupTypeController.swift index ddda9e6e4d..8e62df437a 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupTypeController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupTypeController.swift @@ -11,6 +11,7 @@ public class AAGroupTypeViewController: AAContentTableController { private var linkSection: AAManagedSection! private var publicRow: AACommonRow! private var privateRow: AACommonRow! + private var shortNameRow: AAEditRow! public init(gid: Int) { super.init(style: .SettingsGrouped) @@ -21,7 +22,7 @@ public class AAGroupTypeViewController: AAContentTableController { } else { navigationItem.title = AALocalized("GroupTypeTitle") } - navigationItem.rightBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationSave"), style: .Plain, target: self, action: #selector(saveDidTap)) + navigationItem.rightBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationSave"), style: .Done, target: self, action: #selector(saveDidTap)) } public required init(coder aDecoder: NSCoder) { @@ -29,6 +30,8 @@ public class AAGroupTypeViewController: AAContentTableController { } public override func tableDidLoad() { + + self.isPublic = group.shortName.get() != nil section { (s) in @@ -86,7 +89,12 @@ public class AAGroupTypeViewController: AAContentTableController { } self.linkSection = section { (s) in - s.headerText = "Hey!" + s.footerText = "People can share this link with others and find your channel using search." + self.shortNameRow = s.edit({ (r) in + r.autocapitalizationType = .None + r.prefix = "actor.im/join/" + r.text = self.group.shortName.get() + }) } if !self.isPublic { managedTable.sections.removeAtIndex(1) @@ -94,6 +102,23 @@ public class AAGroupTypeViewController: AAContentTableController { } public func saveDidTap() { + let nShortName: String? + if self.isPublic { + if self.shortNameRow.text!.trim().length > 0 { + nShortName = self.shortNameRow.text!.trim() + } else { + nShortName = nil + } + } else { + nShortName = nil + } + if nShortName != group.shortName.get() { + executePromise(Actor.editGroupShortNameWithGid(jint(self.gid), withAbout: nShortName).then({ (r:ARVoid!) in + self.navigateBack() + })) + } else { + navigateBack() + } } } \ No newline at end of file diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewController.swift index 719eef751b..b6ae21c0d5 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewController.swift @@ -148,7 +148,7 @@ public class AAGroupViewController: AAContentTableController { s.common({ (r) in r.content = AALocalized("GroupAdministration") r.selectAction = { () -> Bool in - self.navigateNext(AAGroupTypeViewController(gid: self.gid)) + self.navigateNext(AAGroupAdministrationViewController(gid: self.gid)) return false } }) diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/ManagedCells.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/ManagedCells.swift index 56a2bf38fd..38b8f7b2c9 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/ManagedCells.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/ManagedCells.swift @@ -8,6 +8,7 @@ import Foundation public class AAEditRow: AAManagedRow, UITextFieldDelegate { + public var prefix: String? public var text: String? public var placeholder: String? public var returnKeyType = UIReturnKeyType.Default @@ -36,6 +37,14 @@ public class AAEditRow: AAManagedRow, UITextFieldDelegate { res.textField.delegate = self res.textField.removeTarget(nil, action: nil, forControlEvents: .AllEvents) res.textField.addTarget(self, action: #selector(AAEditRow.textFieldDidChange(_:)), forControlEvents: .EditingChanged) + + if prefix != nil { + res.textPrefix.text = prefix + res.textPrefix.hidden = false + } else { + res.textPrefix.hidden = true + } + return res } @@ -344,6 +353,10 @@ public class AACommonRow: AAManagedRow { // Binding + public func rangeBind(table: AAManagedTable, binder: AABinder) { + bindAction?(r: self) + } + public override func reload() { bindAction?(r: self) diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/Cells/AACommonCell.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/Cells/AACommonCell.swift index b0216f9f32..74bfe9bd95 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/Cells/AACommonCell.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/Cells/AACommonCell.swift @@ -155,8 +155,13 @@ public class AACommonCell: AATableViewCell { hintLabel.frame = CGRectMake(0, 0, 100, 44) hintLabel.sizeToFit() - hintLabel.frame = CGRectMake(contentView.bounds.width - hintLabel.width, 0, hintLabel.width, 44) - titleLabel.frame = CGRectMake(contentInset, 0, contentView.bounds.width - hintLabel.width - contentInset - 5, 44) + if accessoryType == UITableViewCellAccessoryType.None { + hintLabel.frame = CGRectMake(contentView.bounds.width - hintLabel.width - 15, 0, hintLabel.width, 44) + titleLabel.frame = CGRectMake(contentInset, 0, contentView.bounds.width - hintLabel.width - contentInset - 20, 44) + } else { + hintLabel.frame = CGRectMake(contentView.bounds.width - hintLabel.width, 0, hintLabel.width, 44) + titleLabel.frame = CGRectMake(contentInset, 0, contentView.bounds.width - hintLabel.width - contentInset - 5, 44) + } } else { titleLabel.frame = CGRectMake(contentInset, 0, contentView.bounds.width - contentInset - 5, 44) } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/Cells/AAEditCell.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/Cells/AAEditCell.swift index c57c0eac79..cf9f5a06a9 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/Cells/AAEditCell.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/Cells/AAEditCell.swift @@ -6,6 +6,7 @@ import Foundation public class AAEditCell: AATableViewCell { + public let textPrefix = UILabel() public let textField = UITextField() public override init(style: UITableViewCellStyle, reuseIdentifier: String?) { @@ -16,6 +17,9 @@ public class AAEditCell: AATableViewCell { textField.textColor = appStyle.cellTextColor textField.keyboardAppearance = appStyle.isDarkApp ? .Dark : .Light + textPrefix.hidden = true + + contentView.addSubview(textPrefix) contentView.addSubview(textField) } @@ -26,6 +30,13 @@ public class AAEditCell: AATableViewCell { public override func layoutSubviews() { super.layoutSubviews() - textField.frame = CGRectMake(15, 0, contentView.width - 30, 44) + if textPrefix.hidden { + textField.frame = CGRectMake(15, 0, contentView.width - 30, 44) + } else { + textPrefix.frame = CGRectMake(15, 0, contentView.width - 30, 44) + textPrefix.sizeToFit() + textPrefix.frame = CGRectMake(15, 0, textPrefix.width, 44) + textField.frame = CGRectMake(15 + textPrefix.width, 0, contentView.width - textPrefix.width - 30, 44) + } } } \ No newline at end of file From 14648f3c2d10d9b1842b52da530785ee44e21bbd Mon Sep 17 00:00:00 2001 From: kor_ka Date: Thu, 21 Jul 2016 19:53:47 +0300 Subject: [PATCH 087/414] feat(android): members fragment header actions, loader --- .../controllers/group/MembersFragment.java | 82 ++++++++++++++++++- .../group/view/MembersAdapter.java | 16 +++- .../src/main/res/layout/fragment_members.xml | 8 ++ 3 files changed, 101 insertions(+), 5 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/MembersFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/MembersFragment.java index f767550f98..499dc422ed 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/MembersFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/MembersFragment.java @@ -1,24 +1,38 @@ package im.actor.sdk.controllers.group; +import android.content.Intent; import android.os.Bundle; import android.support.annotation.Nullable; +import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.LinearLayout; import android.widget.ListView; +import android.widget.TextView; import java.util.ArrayList; +import fr.castorflex.android.circularprogressbar.CircularProgressBar; import im.actor.core.entity.GroupMember; +import im.actor.core.viewmodel.GroupVM; +import im.actor.sdk.ActorSDK; import im.actor.sdk.R; import im.actor.sdk.controllers.BaseFragment; +import im.actor.sdk.controllers.Intents; import im.actor.sdk.controllers.group.view.MembersAdapter; +import im.actor.sdk.util.Screen; +import im.actor.sdk.view.DividerView; +import im.actor.sdk.view.adapters.HeaderViewRecyclerAdapter; import im.actor.sdk.view.adapters.RecyclerListView; +import static im.actor.sdk.util.ActorSDKMessenger.groups; import static im.actor.sdk.util.ActorSDKMessenger.messenger; public class MembersFragment extends BaseFragment { + protected CircularProgressBar progressView; + public static MembersFragment create(int groupId) { MembersFragment res = new MembersFragment(); Bundle args = new Bundle(); @@ -40,9 +54,65 @@ public MembersFragment() { @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View res = inflater.inflate(R.layout.fragment_members, container, false); - adapter = new MembersAdapter(getContext(), getArguments().getInt("groupId")); + int groupId = getArguments().getInt("groupId"); + adapter = new MembersAdapter(getContext(), groupId); + + GroupVM groupVM = groups().get(groupId); + RecyclerListView list = (RecyclerListView) res.findViewById(R.id.items); + + + Boolean canInvite = groupVM.getIsCanInviteMembers().get(); + Boolean canInviteViaLink = groupVM.getIsCanInviteViaLink().get(); + if (canInvite || canInviteViaLink) { + LinearLayout header = new LinearLayout(getActivity()); + list.addHeaderView(header); + + if (canInvite) { + TextView addMmemberTV = new TextView(getContext()); + addMmemberTV.setBackgroundResource(R.drawable.selector); + addMmemberTV.setTextSize(16); + addMmemberTV.setPadding(Screen.dp(72), 0, 0, 0); + addMmemberTV.setGravity(Gravity.CENTER_VERTICAL); + addMmemberTV.setText(R.string.group_add_member); + addMmemberTV.setTextColor(ActorSDK.sharedActor().style.getTextPrimaryColor()); + addMmemberTV.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + startActivity(new Intent(getActivity(), AddMemberActivity.class) + .putExtra(Intents.EXTRA_GROUP_ID, groupId)); + } + }); + + header.addView(addMmemberTV, ViewGroup.LayoutParams.MATCH_PARENT, Screen.dp(58)); + } + + if (canInvite && canInviteViaLink) { + header.addView(new DividerView(getActivity()), ViewGroup.LayoutParams.MATCH_PARENT, 1); + } - ((RecyclerListView) res.findViewById(R.id.items)).setAdapter(adapter); + if (canInviteViaLink) { + TextView shareLinkTV = new TextView(getContext()); + shareLinkTV.setBackgroundResource(R.drawable.selector); + shareLinkTV.setTextSize(16); + shareLinkTV.setPadding(Screen.dp(72), 0, 0, 0); + shareLinkTV.setGravity(Gravity.CENTER_VERTICAL); + shareLinkTV.setTextColor(ActorSDK.sharedActor().style.getTextPrimaryColor()); + shareLinkTV.setText(R.string.invite_link_action_share); + shareLinkTV.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Intents.inviteLink(groupId, getActivity()); + } + }); + + header.addView(shareLinkTV, ViewGroup.LayoutParams.MATCH_PARENT, Screen.dp(58)); + } + } + + list.setAdapter(adapter); + + progressView = (CircularProgressBar) res.findViewById(R.id.loadingProgress); + progressView.setIndeterminate(true); return res; } @@ -50,6 +120,12 @@ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Override public void onResume() { super.onResume(); - adapter.initLoad(); + adapter.initLoad(() -> hideView(progressView)); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + adapter.dispose(); } } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/view/MembersAdapter.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/view/MembersAdapter.java index 013996bbba..8a19a48f43 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/view/MembersAdapter.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/view/MembersAdapter.java @@ -9,6 +9,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Optional; import im.actor.sdk.ActorSDK; import im.actor.sdk.R; @@ -31,6 +32,7 @@ public class MembersAdapter extends HolderAdapter { private ActorBinder BINDER = new ActorBinder(); private boolean loadInProgress = false; private boolean loaddedToEnd = false; + private LoadedCallback callback; public MembersAdapter(Context context, int groupId) { super(context); @@ -76,17 +78,27 @@ protected void onBindViewHolder(ViewHolder holder, GroupMember obj, private byte[] nextMembers; private ArrayList rawMembers = new ArrayList<>(); - public void initLoad() { + public void initLoad(LoadedCallback callback) { + this.callback = callback; if (!isInitiallyLoaded) { loadMore(); } } + public interface LoadedCallback { + void onLoaded(); + } + private void loadMore() { if (!loadInProgress && !loaddedToEnd) { loadInProgress = true; messenger().loadMembers(groupId, LIMIT, nextMembers).then(groupMembersSlice -> { - isInitiallyLoaded = true; + if (!isInitiallyLoaded) { + isInitiallyLoaded = true; + if (callback != null) { + callback.onLoaded(); + } + } rawMembers.clear(); rawMembers.addAll(groupMembersSlice.getUids()); nextMembers = groupMembersSlice.getNext(); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_members.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_members.xml index dc3773cbc8..9ee96580ef 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_members.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_members.xml @@ -7,4 +7,12 @@ android:id="@+id/items" android:layout_width="match_parent" android:layout_height="match_parent" /> + + + \ No newline at end of file From 33900add588f0a04611e085aa24777b541e2e5b7 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Thu, 21 Jul 2016 20:02:48 +0300 Subject: [PATCH 088/414] feat(iOS): Group deletion --- .../AAGroupAdministrationViewController.swift | 8 +++++++- .../Managed Runtime/Executions.swift | 3 ++- .../Sources/SwiftExtensions/Promises.swift | 18 ++++++++++++++++++ 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupAdministrationViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupAdministrationViewController.swift index 0f7c13c9ae..4f93446357 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupAdministrationViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupAdministrationViewController.swift @@ -64,7 +64,13 @@ public class AAGroupAdministrationViewController: AAContentTableController { section { (s) in s.footerText = "You will lose all messages in this group" s.danger("Delete Group", closure: { (r) in - + r.selectAction = { () -> Bool in + self.executePromise(Actor.deleteGroupWithGid(jint(self.gid))).after { + let first = self.navigationController!.viewControllers.first! + self.navigationController!.setViewControllers([first], animated: true) + } + return true + } }) } } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/Executions.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/Executions.swift index eceedc8066..67bac6ba36 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/Executions.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/Executions.swift @@ -177,8 +177,9 @@ public extension UIViewController { AAExecutions.execute(command) } - public func executePromise(promise: ARPromise) { + public func executePromise(promise: ARPromise) -> ARPromise { AAExecutions.execute(promise) + return promise } public func executePromise(promise: ARPromise, successBlock: ((val: Any?) -> Void)?, failureBlock: ((val: Any?) -> Void)?) { diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/SwiftExtensions/Promises.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/SwiftExtensions/Promises.swift index c55c2a44c5..efd8e16d36 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/SwiftExtensions/Promises.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/SwiftExtensions/Promises.swift @@ -39,6 +39,11 @@ extension ARPromise { return self } + func after(closure: () -> ()) -> ARPromise { + then(PromiseConsumerEmpty(closure: closure)) + return self + } + func failure(withClosure closure: (JavaLangException!) -> ()) -> ARPromise { failure(PromiseConsumer(closure: closure)) return self @@ -56,4 +61,17 @@ class PromiseConsumer: NSObject, ARConsumer { func applyWithId(t: AnyObject!) { closure(t as? T) } +} + +class PromiseConsumerEmpty: NSObject, ARConsumer { + + let closure: () -> () + + init(closure: () -> ()) { + self.closure = closure + } + + func applyWithId(t: AnyObject!) { + closure() + } } \ No newline at end of file From 46d61e5c3a553805bdd1cca30942d1e566169bcc Mon Sep 17 00:00:00 2001 From: kor_ka Date: Thu, 21 Jul 2016 20:32:44 +0300 Subject: [PATCH 089/414] chore(android): move to new search --- .../search/GlobalSearchBaseFragment.java | 37 +++++++++++++++---- .../main/java/im/actor/core/Messenger.java | 13 +++++++ .../core/modules/search/SearchModule.java | 12 +++++- 3 files changed, 54 insertions(+), 8 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/search/GlobalSearchBaseFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/search/GlobalSearchBaseFragment.java index dec22056b0..879d4a1db3 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/search/GlobalSearchBaseFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/search/GlobalSearchBaseFragment.java @@ -18,9 +18,15 @@ import android.widget.LinearLayout; import android.widget.TextView; +import java.util.List; + +import im.actor.core.entity.Avatar; import im.actor.core.entity.Peer; +import im.actor.core.entity.PeerSearchEntity; +import im.actor.core.entity.PeerType; import im.actor.core.entity.SearchEntity; import im.actor.core.viewmodel.CommandCallback; +import im.actor.core.viewmodel.GroupVM; import im.actor.core.viewmodel.UserVM; import im.actor.runtime.generic.mvvm.BindedDisplayList; import im.actor.runtime.generic.mvvm.DisplayList; @@ -32,7 +38,9 @@ import im.actor.sdk.view.adapters.HeaderViewRecyclerAdapter; import im.actor.sdk.view.adapters.OnItemClickedListener; +import static im.actor.sdk.util.ActorSDKMessenger.groups; import static im.actor.sdk.util.ActorSDKMessenger.messenger; +import static im.actor.sdk.util.ActorSDKMessenger.users; public abstract class GlobalSearchBaseFragment extends BaseFragment { @@ -152,24 +160,38 @@ public boolean onQueryTextChange(String s) { String activeSearchQuery = searchQuery; searchDisplay.initSearch(s.trim().toLowerCase(), false); searchAdapter.setQuery(s.trim().toLowerCase()); - messenger().findUsers(s).start(new CommandCallback() { + messenger().findPeers(s).start(new CommandCallback>() { @Override - public void onResult(UserVM[] res) { + public void onResult(List res) { int footerVisability = footer.getVisibility(); if (searchQuery.equals(activeSearchQuery)) { boolean showResult = false; - UserVM u = null; - if (res.length > 0) { - u = res[0]; + Peer peer = null; + String name = null; + Avatar avatar = null; + if (res.size() > 0) { + peer = res.get(0).getPeer(); + + if (peer.getPeerType() == PeerType.PRIVATE) { + UserVM userVM = users().get(peer.getPeerId()); + name = userVM.getName().getName(); + avatar = userVM.getAvatar().get(); + } else if (peer.getPeerType() == PeerType.GROUP) { + GroupVM groupVM = groups().get(peer.getPeerId()); + name = groupVM.getName().getName(); + avatar = groupVM.getAvatar().get(); + } else { + return; + } showResult = true; for (int i = 0; i < searchDisplay.getSize(); i++) { - if (searchDisplay.getItem(i).getPeer().equals(Peer.user(u.getId()))) + if (searchDisplay.getItem(i).getPeer().equals(peer)) showResult = false; break; } } if (showResult) { - footerSearchHolder.bind(new SearchEntity(Peer.user(u.getId()), 0, u.getAvatar().get(), u.getName().get()), activeSearchQuery, true); + footerSearchHolder.bind(new SearchEntity(peer, 0, avatar, name), activeSearchQuery, true); showView(footer); } else { goneView(footer); @@ -185,6 +207,7 @@ public void onError(Exception e) { } }); + } else { searchDisplay.initEmpty(); goneView(footer); diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java index f84c9163c9..a07d9c5977 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java @@ -1130,6 +1130,19 @@ public Command> findPeers(PeerSearchType type) { .failure(e -> callback.onError(e)); } + /** + * Finding peers by text query + * + * @param query text query + * @return found peers + */ + @ObjectiveCName("findPeersWithQuery:") + public Command> findPeers(String query) { + return callback -> modules.getSearchModule().findPeers(query) + .then(v -> callback.onResult(v)) + .failure(e -> callback.onError(e)); + } + /** * Finding text messages by query * diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/search/SearchModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/search/SearchModule.java index d0d11c3628..2497c4e743 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/search/SearchModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/search/SearchModule.java @@ -100,7 +100,7 @@ private Promise> findMessages(final ApiSearchCondition AbsContent.fromMessage(itm.getResult().getContent())))); } - public Promise> findPeers(final PeerSearchType type) { + public Promise> findPeers(PeerSearchType type) { final ApiSearchPeerType apiType; if (type == PeerSearchType.GROUPS) { apiType = ApiSearchPeerType.GROUPS; @@ -111,6 +111,16 @@ public Promise> findPeers(final PeerSearchType type) { } ArrayList conditions = new ArrayList<>(); conditions.add(new ApiSearchPeerTypeCondition(apiType)); + return findPeers(conditions); + } + + public Promise> findPeers(String query) { + ArrayList conditions = new ArrayList<>(); + conditions.add(new ApiSearchPieceText(query)); + return findPeers(conditions); + } + + public Promise> findPeers(ArrayList conditions) { return api(new RequestPeerSearch(conditions, ApiSupportConfiguration.OPTIMIZATIONS)) .chain(responsePeerSearch -> From eacddcd9deb1ba99c73b72c46d653b693d54a431 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Thu, 21 Jul 2016 21:16:50 +0300 Subject: [PATCH 090/414] feat(core): Migrated to new permissions api --- actor-sdk/sdk-api/actor.json | 64 +++ .../models/im/actor/api/scheme.mps | 54 ++ .../main/java/im/actor/core/api/ApiGroup.java | 18 +- .../java/im/actor/core/api/ApiGroupFull.java | 125 +--- .../core/api/ApiGroupFullPermissions.java | 43 ++ .../actor/core/api/ApiGroupPermissions.java | 35 ++ .../actor/core/api/parser/UpdatesParser.java | 13 +- .../updates/UpdateGroupCanDeleteChanged.java | 70 --- ...pdateGroupCanEditAdminSettingsChanged.java | 70 --- .../UpdateGroupCanEditAdminsChanged.java | 69 --- .../UpdateGroupCanEditUsernameChanged.java | 70 --- .../UpdateGroupCanInviteMembersChanged.java | 70 --- .../updates/UpdateGroupCanInviteViaLink.java | 70 --- .../updates/UpdateGroupCanLeaveChanged.java | 70 --- .../UpdateGroupCanViewAdminsChanged.java | 69 --- .../UpdateGroupCanViewMembersChanged.java | 70 --- ...=> UpdateGroupFullPermissionsChanged.java} | 28 +- ...ava => UpdateGroupPermissionsChanged.java} | 28 +- .../main/java/im/actor/core/entity/Group.java | 543 ++++-------------- .../core/modules/groups/GroupsProcessor.java | 27 +- .../modules/groups/router/GroupRouter.java | 132 +---- .../java/im/actor/core/util/BitMaskUtil.java | 21 + .../java/im/actor/core/viewmodel/GroupVM.java | 20 +- 23 files changed, 423 insertions(+), 1356 deletions(-) create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiGroupFullPermissions.java create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiGroupPermissions.java delete mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanDeleteChanged.java delete mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanEditAdminSettingsChanged.java delete mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanEditAdminsChanged.java delete mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanEditUsernameChanged.java delete mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanInviteMembersChanged.java delete mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanInviteViaLink.java delete mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanLeaveChanged.java delete mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanViewAdminsChanged.java delete mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanViewMembersChanged.java rename actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/{UpdateGroupCanSendMessagesChanged.java => UpdateGroupFullPermissionsChanged.java} (60%) rename actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/{UpdateGroupCanEditInfoChanged.java => UpdateGroupPermissionsChanged.java} (62%) create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/util/BitMaskUtil.java diff --git a/actor-sdk/sdk-api/actor.json b/actor-sdk/sdk-api/actor.json index 40857ff0eb..ac8c351100 100644 --- a/actor-sdk/sdk-api/actor.json +++ b/actor-sdk/sdk-api/actor.json @@ -7876,6 +7876,70 @@ ] } }, + { + "type": "enum", + "content": { + "name": "GroupPermissions", + "values": [ + { + "name": "SEND_MESSAGE", + "id": 1 + }, + { + "name": "CLEAR", + "id": 2 + }, + { + "name": "LEAVE", + "id": 3 + }, + { + "name": "DELETE", + "id": 4 + } + ] + } + }, + { + "type": "enum", + "content": { + "name": "GroupFullPermissions", + "values": [ + { + "name": "EDIT_INFO", + "id": 1 + }, + { + "name": "VIEW_MEMBERS", + "id": 2 + }, + { + "name": "INVITE_MEMBERS", + "id": 3 + }, + { + "name": "INVITE_VIA_LINK", + "id": 4 + }, + { + "name": "CALL", + "id": 5 + }, + { + "name": "EDIT_ADMIN_SETTINGS", + "id": 6 + }, + { + "name": "VIEW_ADMINS", + "id": 7 + }, + { + "name": "EDIT_ADMINS", + "id": 8 + } + ] + } + }, { "type": "struct", "content": { diff --git a/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps b/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps index 1c08c51b9f..f25e2f6eba 100644 --- a/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps +++ b/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps @@ -6921,6 +6921,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiGroup.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiGroup.java index 8d63742b79..57cbf4d365 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiGroup.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiGroup.java @@ -24,11 +24,11 @@ public class ApiGroup extends BserObject { private Boolean isMember; private Boolean isHidden; private ApiGroupType groupType; - private Boolean canSendMessage; + private Long permissions; private Boolean isDeleted; private ApiMapValue ext; - public ApiGroup(int id, long accessHash, @NotNull String title, @Nullable ApiAvatar avatar, @Nullable Integer membersCount, @Nullable Boolean isMember, @Nullable Boolean isHidden, @Nullable ApiGroupType groupType, @Nullable Boolean canSendMessage, @Nullable Boolean isDeleted, @Nullable ApiMapValue ext) { + public ApiGroup(int id, long accessHash, @NotNull String title, @Nullable ApiAvatar avatar, @Nullable Integer membersCount, @Nullable Boolean isMember, @Nullable Boolean isHidden, @Nullable ApiGroupType groupType, @Nullable Long permissions, @Nullable Boolean isDeleted, @Nullable ApiMapValue ext) { this.id = id; this.accessHash = accessHash; this.title = title; @@ -37,7 +37,7 @@ public ApiGroup(int id, long accessHash, @NotNull String title, @Nullable ApiAva this.isMember = isMember; this.isHidden = isHidden; this.groupType = groupType; - this.canSendMessage = canSendMessage; + this.permissions = permissions; this.isDeleted = isDeleted; this.ext = ext; } @@ -85,8 +85,8 @@ public ApiGroupType getGroupType() { } @Nullable - public Boolean canSendMessage() { - return this.canSendMessage; + public Long getPermissions() { + return this.permissions; } @Nullable @@ -112,7 +112,7 @@ public void parse(BserValues values) throws IOException { if (val_groupType != 0) { this.groupType = ApiGroupType.parse(val_groupType); } - this.canSendMessage = values.optBool(26); + this.permissions = values.optLong(26); this.isDeleted = values.optBool(27); this.ext = values.optObj(22, new ApiMapValue()); if (values.hasRemaining()) { @@ -143,8 +143,8 @@ public void serialize(BserWriter writer) throws IOException { if (this.groupType != null) { writer.writeInt(25, this.groupType.getValue()); } - if (this.canSendMessage != null) { - writer.writeBool(26, this.canSendMessage); + if (this.permissions != null) { + writer.writeLong(26, this.permissions); } if (this.isDeleted != null) { writer.writeBool(27, this.isDeleted); @@ -171,7 +171,7 @@ public String toString() { res += ", isMember=" + this.isMember; res += ", isHidden=" + this.isHidden; res += ", groupType=" + this.groupType; - res += ", canSendMessage=" + this.canSendMessage; + res += ", permissions=" + this.permissions; res += ", isDeleted=" + this.isDeleted; res += ", ext=" + this.ext; res += "}"; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiGroupFull.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiGroupFull.java index c09f0f4ff5..fe8aeb83de 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiGroupFull.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiGroupFull.java @@ -24,20 +24,11 @@ public class ApiGroupFull extends BserObject { private String about; private ApiMapValue ext; private Boolean isAsyncMembers; - private Boolean canViewMembers; - private Boolean canInvitePeople; private Boolean isSharedHistory; - private Boolean canEditGroupInfo; private String shortName; - private Boolean canEditShortName; - private Boolean canEditAdminList; - private Boolean canViewAdminList; - private Boolean canEditAdminSettings; - private Boolean canInviteViaLink; - private Boolean canDelete; - private Boolean canLeave; + private Long permissions; - public ApiGroupFull(int id, long createDate, @Nullable Integer ownerUid, @NotNull List members, @Nullable String theme, @Nullable String about, @Nullable ApiMapValue ext, @Nullable Boolean isAsyncMembers, @Nullable Boolean canViewMembers, @Nullable Boolean canInvitePeople, @Nullable Boolean isSharedHistory, @Nullable Boolean canEditGroupInfo, @Nullable String shortName, @Nullable Boolean canEditShortName, @Nullable Boolean canEditAdminList, @Nullable Boolean canViewAdminList, @Nullable Boolean canEditAdminSettings, @Nullable Boolean canInviteViaLink, @Nullable Boolean canDelete, @Nullable Boolean canLeave) { + public ApiGroupFull(int id, long createDate, @Nullable Integer ownerUid, @NotNull List members, @Nullable String theme, @Nullable String about, @Nullable ApiMapValue ext, @Nullable Boolean isAsyncMembers, @Nullable Boolean isSharedHistory, @Nullable String shortName, @Nullable Long permissions) { this.id = id; this.createDate = createDate; this.ownerUid = ownerUid; @@ -46,18 +37,9 @@ public ApiGroupFull(int id, long createDate, @Nullable Integer ownerUid, @NotNul this.about = about; this.ext = ext; this.isAsyncMembers = isAsyncMembers; - this.canViewMembers = canViewMembers; - this.canInvitePeople = canInvitePeople; this.isSharedHistory = isSharedHistory; - this.canEditGroupInfo = canEditGroupInfo; this.shortName = shortName; - this.canEditShortName = canEditShortName; - this.canEditAdminList = canEditAdminList; - this.canViewAdminList = canViewAdminList; - this.canEditAdminSettings = canEditAdminSettings; - this.canInviteViaLink = canInviteViaLink; - this.canDelete = canDelete; - this.canLeave = canLeave; + this.permissions = permissions; } public ApiGroupFull() { @@ -102,64 +84,19 @@ public Boolean isAsyncMembers() { return this.isAsyncMembers; } - @Nullable - public Boolean canViewMembers() { - return this.canViewMembers; - } - - @Nullable - public Boolean canInvitePeople() { - return this.canInvitePeople; - } - @Nullable public Boolean isSharedHistory() { return this.isSharedHistory; } - @Nullable - public Boolean canEditGroupInfo() { - return this.canEditGroupInfo; - } - @Nullable public String getShortName() { return this.shortName; } @Nullable - public Boolean canEditShortName() { - return this.canEditShortName; - } - - @Nullable - public Boolean canEditAdminList() { - return this.canEditAdminList; - } - - @Nullable - public Boolean canViewAdminList() { - return this.canViewAdminList; - } - - @Nullable - public Boolean canEditAdminSettings() { - return this.canEditAdminSettings; - } - - @Nullable - public Boolean canInviteViaLink() { - return this.canInviteViaLink; - } - - @Nullable - public Boolean canDelete() { - return this.canDelete; - } - - @Nullable - public Boolean canLeave() { - return this.canLeave; + public Long getPermissions() { + return this.permissions; } @Override @@ -176,18 +113,9 @@ public void parse(BserValues values) throws IOException { this.about = values.optString(3); this.ext = values.optObj(7, new ApiMapValue()); this.isAsyncMembers = values.optBool(11); - this.canViewMembers = values.optBool(8); - this.canInvitePeople = values.optBool(9); this.isSharedHistory = values.optBool(10); - this.canEditGroupInfo = values.optBool(13); this.shortName = values.optString(14); - this.canEditShortName = values.optBool(15); - this.canEditAdminList = values.optBool(16); - this.canViewAdminList = values.optBool(17); - this.canEditAdminSettings = values.optBool(18); - this.canInviteViaLink = values.optBool(19); - this.canDelete = values.optBool(20); - this.canLeave = values.optBool(21); + this.permissions = values.optLong(27); if (values.hasRemaining()) { setUnmappedObjects(values.buildRemaining()); } @@ -213,41 +141,14 @@ public void serialize(BserWriter writer) throws IOException { if (this.isAsyncMembers != null) { writer.writeBool(11, this.isAsyncMembers); } - if (this.canViewMembers != null) { - writer.writeBool(8, this.canViewMembers); - } - if (this.canInvitePeople != null) { - writer.writeBool(9, this.canInvitePeople); - } if (this.isSharedHistory != null) { writer.writeBool(10, this.isSharedHistory); } - if (this.canEditGroupInfo != null) { - writer.writeBool(13, this.canEditGroupInfo); - } if (this.shortName != null) { writer.writeString(14, this.shortName); } - if (this.canEditShortName != null) { - writer.writeBool(15, this.canEditShortName); - } - if (this.canEditAdminList != null) { - writer.writeBool(16, this.canEditAdminList); - } - if (this.canViewAdminList != null) { - writer.writeBool(17, this.canViewAdminList); - } - if (this.canEditAdminSettings != null) { - writer.writeBool(18, this.canEditAdminSettings); - } - if (this.canInviteViaLink != null) { - writer.writeBool(19, this.canInviteViaLink); - } - if (this.canDelete != null) { - writer.writeBool(20, this.canDelete); - } - if (this.canLeave != null) { - writer.writeBool(21, this.canLeave); + if (this.permissions != null) { + writer.writeLong(27, this.permissions); } if (this.getUnmappedObjects() != null) { SparseArray unmapped = this.getUnmappedObjects(); @@ -268,17 +169,9 @@ public String toString() { res += ", theme=" + this.theme; res += ", about=" + this.about; res += ", isAsyncMembers=" + this.isAsyncMembers; - res += ", canViewMembers=" + this.canViewMembers; - res += ", canInvitePeople=" + this.canInvitePeople; res += ", isSharedHistory=" + this.isSharedHistory; - res += ", canEditGroupInfo=" + this.canEditGroupInfo; res += ", shortName=" + this.shortName; - res += ", canEditShortName=" + this.canEditShortName; - res += ", canEditAdminList=" + this.canEditAdminList; - res += ", canViewAdminList=" + this.canViewAdminList; - res += ", canEditAdminSettings=" + this.canEditAdminSettings; - res += ", canInviteViaLink=" + this.canInviteViaLink; - res += ", canDelete=" + this.canDelete; + res += ", permissions=" + this.permissions; res += "}"; return res; } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiGroupFullPermissions.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiGroupFullPermissions.java new file mode 100644 index 0000000000..1c015ddb01 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiGroupFullPermissions.java @@ -0,0 +1,43 @@ +package im.actor.core.api; +/* + * Generated by the Actor API Scheme generator. DO NOT EDIT! + */ + +import java.io.IOException; + +public enum ApiGroupFullPermissions { + + EDIT_INFO(1), + VIEW_MEMBERS(2), + INVITE_MEMBERS(3), + INVITE_VIA_LINK(4), + CALL(5), + EDIT_ADMIN_SETTINGS(6), + VIEW_ADMINS(7), + EDIT_ADMINS(8), + UNSUPPORTED_VALUE(-1); + + private int value; + + ApiGroupFullPermissions(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + public static ApiGroupFullPermissions parse(int value) throws IOException { + switch(value) { + case 1: return ApiGroupFullPermissions.EDIT_INFO; + case 2: return ApiGroupFullPermissions.VIEW_MEMBERS; + case 3: return ApiGroupFullPermissions.INVITE_MEMBERS; + case 4: return ApiGroupFullPermissions.INVITE_VIA_LINK; + case 5: return ApiGroupFullPermissions.CALL; + case 6: return ApiGroupFullPermissions.EDIT_ADMIN_SETTINGS; + case 7: return ApiGroupFullPermissions.VIEW_ADMINS; + case 8: return ApiGroupFullPermissions.EDIT_ADMINS; + default: return ApiGroupFullPermissions.UNSUPPORTED_VALUE; + } + } +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiGroupPermissions.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiGroupPermissions.java new file mode 100644 index 0000000000..9679c52923 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiGroupPermissions.java @@ -0,0 +1,35 @@ +package im.actor.core.api; +/* + * Generated by the Actor API Scheme generator. DO NOT EDIT! + */ + +import java.io.IOException; + +public enum ApiGroupPermissions { + + SEND_MESSAGE(1), + CLEAR(2), + LEAVE(3), + DELETE(4), + UNSUPPORTED_VALUE(-1); + + private int value; + + ApiGroupPermissions(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + public static ApiGroupPermissions parse(int value) throws IOException { + switch(value) { + case 1: return ApiGroupPermissions.SEND_MESSAGE; + case 2: return ApiGroupPermissions.CLEAR; + case 3: return ApiGroupPermissions.LEAVE; + case 4: return ApiGroupPermissions.DELETE; + default: return ApiGroupPermissions.UNSUPPORTED_VALUE; + } + } +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/parser/UpdatesParser.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/parser/UpdatesParser.java index 9963a12115..1f1dcd4a0e 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/parser/UpdatesParser.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/parser/UpdatesParser.java @@ -58,18 +58,9 @@ public Update read(int type, byte[] payload) throws IOException { case 2628: return UpdateGroupShortNameChanged.fromBytes(payload); case 2619: return UpdateGroupOwnerChanged.fromBytes(payload); case 2620: return UpdateGroupHistoryShared.fromBytes(payload); - case 2624: return UpdateGroupCanSendMessagesChanged.fromBytes(payload); - case 2625: return UpdateGroupCanViewMembersChanged.fromBytes(payload); - case 2626: return UpdateGroupCanInviteMembersChanged.fromBytes(payload); - case 2631: return UpdateGroupCanEditInfoChanged.fromBytes(payload); - case 2632: return UpdateGroupCanEditUsernameChanged.fromBytes(payload); - case 2633: return UpdateGroupCanEditAdminsChanged.fromBytes(payload); - case 2640: return UpdateGroupCanViewAdminsChanged.fromBytes(payload); - case 2646: return UpdateGroupCanInviteViaLink.fromBytes(payload); - case 2647: return UpdateGroupCanLeaveChanged.fromBytes(payload); - case 2648: return UpdateGroupCanDeleteChanged.fromBytes(payload); - case 2641: return UpdateGroupCanEditAdminSettingsChanged.fromBytes(payload); case 2658: return UpdateGroupDeleted.fromBytes(payload); + case 2663: return UpdateGroupPermissionsChanged.fromBytes(payload); + case 2664: return UpdateGroupFullPermissionsChanged.fromBytes(payload); case 2612: return UpdateGroupMemberChanged.fromBytes(payload); case 2615: return UpdateGroupMembersBecameAsync.fromBytes(payload); case 2614: return UpdateGroupMembersUpdated.fromBytes(payload); diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanDeleteChanged.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanDeleteChanged.java deleted file mode 100644 index 788d88bfcb..0000000000 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanDeleteChanged.java +++ /dev/null @@ -1,70 +0,0 @@ -package im.actor.core.api.updates; -/* - * Generated by the Actor API Scheme generator. DO NOT EDIT! - */ - -import im.actor.runtime.bser.*; -import im.actor.runtime.collections.*; -import static im.actor.runtime.bser.Utils.*; -import im.actor.core.network.parser.*; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.NotNull; -import com.google.j2objc.annotations.ObjectiveCName; -import java.io.IOException; -import java.util.List; -import java.util.ArrayList; -import im.actor.core.api.*; - -public class UpdateGroupCanDeleteChanged extends Update { - - public static final int HEADER = 0xa58; - public static UpdateGroupCanDeleteChanged fromBytes(byte[] data) throws IOException { - return Bser.parse(new UpdateGroupCanDeleteChanged(), data); - } - - private int groupId; - private boolean canDeleteChanged; - - public UpdateGroupCanDeleteChanged(int groupId, boolean canDeleteChanged) { - this.groupId = groupId; - this.canDeleteChanged = canDeleteChanged; - } - - public UpdateGroupCanDeleteChanged() { - - } - - public int getGroupId() { - return this.groupId; - } - - public boolean canDeleteChanged() { - return this.canDeleteChanged; - } - - @Override - public void parse(BserValues values) throws IOException { - this.groupId = values.getInt(1); - this.canDeleteChanged = values.getBool(2); - } - - @Override - public void serialize(BserWriter writer) throws IOException { - writer.writeInt(1, this.groupId); - writer.writeBool(2, this.canDeleteChanged); - } - - @Override - public String toString() { - String res = "update GroupCanDeleteChanged{"; - res += "groupId=" + this.groupId; - res += ", canDeleteChanged=" + this.canDeleteChanged; - res += "}"; - return res; - } - - @Override - public int getHeaderKey() { - return HEADER; - } -} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanEditAdminSettingsChanged.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanEditAdminSettingsChanged.java deleted file mode 100644 index 364b5f462a..0000000000 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanEditAdminSettingsChanged.java +++ /dev/null @@ -1,70 +0,0 @@ -package im.actor.core.api.updates; -/* - * Generated by the Actor API Scheme generator. DO NOT EDIT! - */ - -import im.actor.runtime.bser.*; -import im.actor.runtime.collections.*; -import static im.actor.runtime.bser.Utils.*; -import im.actor.core.network.parser.*; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.NotNull; -import com.google.j2objc.annotations.ObjectiveCName; -import java.io.IOException; -import java.util.List; -import java.util.ArrayList; -import im.actor.core.api.*; - -public class UpdateGroupCanEditAdminSettingsChanged extends Update { - - public static final int HEADER = 0xa51; - public static UpdateGroupCanEditAdminSettingsChanged fromBytes(byte[] data) throws IOException { - return Bser.parse(new UpdateGroupCanEditAdminSettingsChanged(), data); - } - - private int groupId; - private boolean canEditAdminSettings; - - public UpdateGroupCanEditAdminSettingsChanged(int groupId, boolean canEditAdminSettings) { - this.groupId = groupId; - this.canEditAdminSettings = canEditAdminSettings; - } - - public UpdateGroupCanEditAdminSettingsChanged() { - - } - - public int getGroupId() { - return this.groupId; - } - - public boolean canEditAdminSettings() { - return this.canEditAdminSettings; - } - - @Override - public void parse(BserValues values) throws IOException { - this.groupId = values.getInt(1); - this.canEditAdminSettings = values.getBool(2); - } - - @Override - public void serialize(BserWriter writer) throws IOException { - writer.writeInt(1, this.groupId); - writer.writeBool(2, this.canEditAdminSettings); - } - - @Override - public String toString() { - String res = "update GroupCanEditAdminSettingsChanged{"; - res += "groupId=" + this.groupId; - res += ", canEditAdminSettings=" + this.canEditAdminSettings; - res += "}"; - return res; - } - - @Override - public int getHeaderKey() { - return HEADER; - } -} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanEditAdminsChanged.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanEditAdminsChanged.java deleted file mode 100644 index a243f19194..0000000000 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanEditAdminsChanged.java +++ /dev/null @@ -1,69 +0,0 @@ -package im.actor.core.api.updates; -/* - * Generated by the Actor API Scheme generator. DO NOT EDIT! - */ - -import im.actor.runtime.bser.*; -import im.actor.runtime.collections.*; -import static im.actor.runtime.bser.Utils.*; -import im.actor.core.network.parser.*; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.NotNull; -import com.google.j2objc.annotations.ObjectiveCName; -import java.io.IOException; -import java.util.List; -import java.util.ArrayList; -import im.actor.core.api.*; - -public class UpdateGroupCanEditAdminsChanged extends Update { - - public static final int HEADER = 0xa49; - public static UpdateGroupCanEditAdminsChanged fromBytes(byte[] data) throws IOException { - return Bser.parse(new UpdateGroupCanEditAdminsChanged(), data); - } - - private int groupId; - private boolean canAssignAdmins; - - public UpdateGroupCanEditAdminsChanged(int groupId, boolean canAssignAdmins) { - this.groupId = groupId; - this.canAssignAdmins = canAssignAdmins; - } - - public UpdateGroupCanEditAdminsChanged() { - - } - - public int getGroupId() { - return this.groupId; - } - - public boolean canAssignAdmins() { - return this.canAssignAdmins; - } - - @Override - public void parse(BserValues values) throws IOException { - this.groupId = values.getInt(1); - this.canAssignAdmins = values.getBool(2); - } - - @Override - public void serialize(BserWriter writer) throws IOException { - writer.writeInt(1, this.groupId); - writer.writeBool(2, this.canAssignAdmins); - } - - @Override - public String toString() { - String res = "update GroupCanEditAdminsChanged{"; - res += "groupId=" + this.groupId; - res += "}"; - return res; - } - - @Override - public int getHeaderKey() { - return HEADER; - } -} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanEditUsernameChanged.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanEditUsernameChanged.java deleted file mode 100644 index 03097d4618..0000000000 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanEditUsernameChanged.java +++ /dev/null @@ -1,70 +0,0 @@ -package im.actor.core.api.updates; -/* - * Generated by the Actor API Scheme generator. DO NOT EDIT! - */ - -import im.actor.runtime.bser.*; -import im.actor.runtime.collections.*; -import static im.actor.runtime.bser.Utils.*; -import im.actor.core.network.parser.*; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.NotNull; -import com.google.j2objc.annotations.ObjectiveCName; -import java.io.IOException; -import java.util.List; -import java.util.ArrayList; -import im.actor.core.api.*; - -public class UpdateGroupCanEditUsernameChanged extends Update { - - public static final int HEADER = 0xa48; - public static UpdateGroupCanEditUsernameChanged fromBytes(byte[] data) throws IOException { - return Bser.parse(new UpdateGroupCanEditUsernameChanged(), data); - } - - private int groupId; - private boolean canEditUsername; - - public UpdateGroupCanEditUsernameChanged(int groupId, boolean canEditUsername) { - this.groupId = groupId; - this.canEditUsername = canEditUsername; - } - - public UpdateGroupCanEditUsernameChanged() { - - } - - public int getGroupId() { - return this.groupId; - } - - public boolean canEditUsername() { - return this.canEditUsername; - } - - @Override - public void parse(BserValues values) throws IOException { - this.groupId = values.getInt(1); - this.canEditUsername = values.getBool(2); - } - - @Override - public void serialize(BserWriter writer) throws IOException { - writer.writeInt(1, this.groupId); - writer.writeBool(2, this.canEditUsername); - } - - @Override - public String toString() { - String res = "update GroupCanEditUsernameChanged{"; - res += "groupId=" + this.groupId; - res += ", canEditUsername=" + this.canEditUsername; - res += "}"; - return res; - } - - @Override - public int getHeaderKey() { - return HEADER; - } -} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanInviteMembersChanged.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanInviteMembersChanged.java deleted file mode 100644 index 473620779d..0000000000 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanInviteMembersChanged.java +++ /dev/null @@ -1,70 +0,0 @@ -package im.actor.core.api.updates; -/* - * Generated by the Actor API Scheme generator. DO NOT EDIT! - */ - -import im.actor.runtime.bser.*; -import im.actor.runtime.collections.*; -import static im.actor.runtime.bser.Utils.*; -import im.actor.core.network.parser.*; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.NotNull; -import com.google.j2objc.annotations.ObjectiveCName; -import java.io.IOException; -import java.util.List; -import java.util.ArrayList; -import im.actor.core.api.*; - -public class UpdateGroupCanInviteMembersChanged extends Update { - - public static final int HEADER = 0xa42; - public static UpdateGroupCanInviteMembersChanged fromBytes(byte[] data) throws IOException { - return Bser.parse(new UpdateGroupCanInviteMembersChanged(), data); - } - - private int groupId; - private boolean canInviteMembers; - - public UpdateGroupCanInviteMembersChanged(int groupId, boolean canInviteMembers) { - this.groupId = groupId; - this.canInviteMembers = canInviteMembers; - } - - public UpdateGroupCanInviteMembersChanged() { - - } - - public int getGroupId() { - return this.groupId; - } - - public boolean canInviteMembers() { - return this.canInviteMembers; - } - - @Override - public void parse(BserValues values) throws IOException { - this.groupId = values.getInt(1); - this.canInviteMembers = values.getBool(2); - } - - @Override - public void serialize(BserWriter writer) throws IOException { - writer.writeInt(1, this.groupId); - writer.writeBool(2, this.canInviteMembers); - } - - @Override - public String toString() { - String res = "update GroupCanInviteMembersChanged{"; - res += "groupId=" + this.groupId; - res += ", canInviteMembers=" + this.canInviteMembers; - res += "}"; - return res; - } - - @Override - public int getHeaderKey() { - return HEADER; - } -} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanInviteViaLink.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanInviteViaLink.java deleted file mode 100644 index 3f94525a23..0000000000 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanInviteViaLink.java +++ /dev/null @@ -1,70 +0,0 @@ -package im.actor.core.api.updates; -/* - * Generated by the Actor API Scheme generator. DO NOT EDIT! - */ - -import im.actor.runtime.bser.*; -import im.actor.runtime.collections.*; -import static im.actor.runtime.bser.Utils.*; -import im.actor.core.network.parser.*; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.NotNull; -import com.google.j2objc.annotations.ObjectiveCName; -import java.io.IOException; -import java.util.List; -import java.util.ArrayList; -import im.actor.core.api.*; - -public class UpdateGroupCanInviteViaLink extends Update { - - public static final int HEADER = 0xa56; - public static UpdateGroupCanInviteViaLink fromBytes(byte[] data) throws IOException { - return Bser.parse(new UpdateGroupCanInviteViaLink(), data); - } - - private int groupId; - private boolean canInviteViaLink; - - public UpdateGroupCanInviteViaLink(int groupId, boolean canInviteViaLink) { - this.groupId = groupId; - this.canInviteViaLink = canInviteViaLink; - } - - public UpdateGroupCanInviteViaLink() { - - } - - public int getGroupId() { - return this.groupId; - } - - public boolean canInviteViaLink() { - return this.canInviteViaLink; - } - - @Override - public void parse(BserValues values) throws IOException { - this.groupId = values.getInt(1); - this.canInviteViaLink = values.getBool(2); - } - - @Override - public void serialize(BserWriter writer) throws IOException { - writer.writeInt(1, this.groupId); - writer.writeBool(2, this.canInviteViaLink); - } - - @Override - public String toString() { - String res = "update GroupCanInviteViaLink{"; - res += "groupId=" + this.groupId; - res += ", canInviteViaLink=" + this.canInviteViaLink; - res += "}"; - return res; - } - - @Override - public int getHeaderKey() { - return HEADER; - } -} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanLeaveChanged.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanLeaveChanged.java deleted file mode 100644 index cea8e92a23..0000000000 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanLeaveChanged.java +++ /dev/null @@ -1,70 +0,0 @@ -package im.actor.core.api.updates; -/* - * Generated by the Actor API Scheme generator. DO NOT EDIT! - */ - -import im.actor.runtime.bser.*; -import im.actor.runtime.collections.*; -import static im.actor.runtime.bser.Utils.*; -import im.actor.core.network.parser.*; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.NotNull; -import com.google.j2objc.annotations.ObjectiveCName; -import java.io.IOException; -import java.util.List; -import java.util.ArrayList; -import im.actor.core.api.*; - -public class UpdateGroupCanLeaveChanged extends Update { - - public static final int HEADER = 0xa57; - public static UpdateGroupCanLeaveChanged fromBytes(byte[] data) throws IOException { - return Bser.parse(new UpdateGroupCanLeaveChanged(), data); - } - - private int groupId; - private boolean canLeaveChanged; - - public UpdateGroupCanLeaveChanged(int groupId, boolean canLeaveChanged) { - this.groupId = groupId; - this.canLeaveChanged = canLeaveChanged; - } - - public UpdateGroupCanLeaveChanged() { - - } - - public int getGroupId() { - return this.groupId; - } - - public boolean canLeaveChanged() { - return this.canLeaveChanged; - } - - @Override - public void parse(BserValues values) throws IOException { - this.groupId = values.getInt(1); - this.canLeaveChanged = values.getBool(2); - } - - @Override - public void serialize(BserWriter writer) throws IOException { - writer.writeInt(1, this.groupId); - writer.writeBool(2, this.canLeaveChanged); - } - - @Override - public String toString() { - String res = "update GroupCanLeaveChanged{"; - res += "groupId=" + this.groupId; - res += ", canLeaveChanged=" + this.canLeaveChanged; - res += "}"; - return res; - } - - @Override - public int getHeaderKey() { - return HEADER; - } -} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanViewAdminsChanged.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanViewAdminsChanged.java deleted file mode 100644 index 4d6806d951..0000000000 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanViewAdminsChanged.java +++ /dev/null @@ -1,69 +0,0 @@ -package im.actor.core.api.updates; -/* - * Generated by the Actor API Scheme generator. DO NOT EDIT! - */ - -import im.actor.runtime.bser.*; -import im.actor.runtime.collections.*; -import static im.actor.runtime.bser.Utils.*; -import im.actor.core.network.parser.*; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.NotNull; -import com.google.j2objc.annotations.ObjectiveCName; -import java.io.IOException; -import java.util.List; -import java.util.ArrayList; -import im.actor.core.api.*; - -public class UpdateGroupCanViewAdminsChanged extends Update { - - public static final int HEADER = 0xa50; - public static UpdateGroupCanViewAdminsChanged fromBytes(byte[] data) throws IOException { - return Bser.parse(new UpdateGroupCanViewAdminsChanged(), data); - } - - private int groupId; - private boolean canViewAdmins; - - public UpdateGroupCanViewAdminsChanged(int groupId, boolean canViewAdmins) { - this.groupId = groupId; - this.canViewAdmins = canViewAdmins; - } - - public UpdateGroupCanViewAdminsChanged() { - - } - - public int getGroupId() { - return this.groupId; - } - - public boolean canViewAdmins() { - return this.canViewAdmins; - } - - @Override - public void parse(BserValues values) throws IOException { - this.groupId = values.getInt(1); - this.canViewAdmins = values.getBool(2); - } - - @Override - public void serialize(BserWriter writer) throws IOException { - writer.writeInt(1, this.groupId); - writer.writeBool(2, this.canViewAdmins); - } - - @Override - public String toString() { - String res = "update GroupCanViewAdminsChanged{"; - res += "groupId=" + this.groupId; - res += "}"; - return res; - } - - @Override - public int getHeaderKey() { - return HEADER; - } -} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanViewMembersChanged.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanViewMembersChanged.java deleted file mode 100644 index 8b117a5eb6..0000000000 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanViewMembersChanged.java +++ /dev/null @@ -1,70 +0,0 @@ -package im.actor.core.api.updates; -/* - * Generated by the Actor API Scheme generator. DO NOT EDIT! - */ - -import im.actor.runtime.bser.*; -import im.actor.runtime.collections.*; -import static im.actor.runtime.bser.Utils.*; -import im.actor.core.network.parser.*; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.NotNull; -import com.google.j2objc.annotations.ObjectiveCName; -import java.io.IOException; -import java.util.List; -import java.util.ArrayList; -import im.actor.core.api.*; - -public class UpdateGroupCanViewMembersChanged extends Update { - - public static final int HEADER = 0xa41; - public static UpdateGroupCanViewMembersChanged fromBytes(byte[] data) throws IOException { - return Bser.parse(new UpdateGroupCanViewMembersChanged(), data); - } - - private int groupId; - private boolean canViewMembers; - - public UpdateGroupCanViewMembersChanged(int groupId, boolean canViewMembers) { - this.groupId = groupId; - this.canViewMembers = canViewMembers; - } - - public UpdateGroupCanViewMembersChanged() { - - } - - public int getGroupId() { - return this.groupId; - } - - public boolean canViewMembers() { - return this.canViewMembers; - } - - @Override - public void parse(BserValues values) throws IOException { - this.groupId = values.getInt(1); - this.canViewMembers = values.getBool(2); - } - - @Override - public void serialize(BserWriter writer) throws IOException { - writer.writeInt(1, this.groupId); - writer.writeBool(2, this.canViewMembers); - } - - @Override - public String toString() { - String res = "update GroupCanViewMembersChanged{"; - res += "groupId=" + this.groupId; - res += ", canViewMembers=" + this.canViewMembers; - res += "}"; - return res; - } - - @Override - public int getHeaderKey() { - return HEADER; - } -} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanSendMessagesChanged.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupFullPermissionsChanged.java similarity index 60% rename from actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanSendMessagesChanged.java rename to actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupFullPermissionsChanged.java index dad4c9cf8f..a539daf72e 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanSendMessagesChanged.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupFullPermissionsChanged.java @@ -15,22 +15,22 @@ import java.util.ArrayList; import im.actor.core.api.*; -public class UpdateGroupCanSendMessagesChanged extends Update { +public class UpdateGroupFullPermissionsChanged extends Update { - public static final int HEADER = 0xa40; - public static UpdateGroupCanSendMessagesChanged fromBytes(byte[] data) throws IOException { - return Bser.parse(new UpdateGroupCanSendMessagesChanged(), data); + public static final int HEADER = 0xa68; + public static UpdateGroupFullPermissionsChanged fromBytes(byte[] data) throws IOException { + return Bser.parse(new UpdateGroupFullPermissionsChanged(), data); } private int groupId; - private boolean canSendMessages; + private long permissions; - public UpdateGroupCanSendMessagesChanged(int groupId, boolean canSendMessages) { + public UpdateGroupFullPermissionsChanged(int groupId, long permissions) { this.groupId = groupId; - this.canSendMessages = canSendMessages; + this.permissions = permissions; } - public UpdateGroupCanSendMessagesChanged() { + public UpdateGroupFullPermissionsChanged() { } @@ -38,27 +38,27 @@ public int getGroupId() { return this.groupId; } - public boolean canSendMessages() { - return this.canSendMessages; + public long getPermissions() { + return this.permissions; } @Override public void parse(BserValues values) throws IOException { this.groupId = values.getInt(1); - this.canSendMessages = values.getBool(2); + this.permissions = values.getLong(2); } @Override public void serialize(BserWriter writer) throws IOException { writer.writeInt(1, this.groupId); - writer.writeBool(2, this.canSendMessages); + writer.writeLong(2, this.permissions); } @Override public String toString() { - String res = "update GroupCanSendMessagesChanged{"; + String res = "update GroupFullPermissionsChanged{"; res += "groupId=" + this.groupId; - res += ", canSendMessages=" + this.canSendMessages; + res += ", permissions=" + this.permissions; res += "}"; return res; } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanEditInfoChanged.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupPermissionsChanged.java similarity index 62% rename from actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanEditInfoChanged.java rename to actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupPermissionsChanged.java index 66ed344c64..02f54ec56d 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupCanEditInfoChanged.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupPermissionsChanged.java @@ -15,22 +15,22 @@ import java.util.ArrayList; import im.actor.core.api.*; -public class UpdateGroupCanEditInfoChanged extends Update { +public class UpdateGroupPermissionsChanged extends Update { - public static final int HEADER = 0xa47; - public static UpdateGroupCanEditInfoChanged fromBytes(byte[] data) throws IOException { - return Bser.parse(new UpdateGroupCanEditInfoChanged(), data); + public static final int HEADER = 0xa67; + public static UpdateGroupPermissionsChanged fromBytes(byte[] data) throws IOException { + return Bser.parse(new UpdateGroupPermissionsChanged(), data); } private int groupId; - private boolean canEditGroup; + private long permissions; - public UpdateGroupCanEditInfoChanged(int groupId, boolean canEditGroup) { + public UpdateGroupPermissionsChanged(int groupId, long permissions) { this.groupId = groupId; - this.canEditGroup = canEditGroup; + this.permissions = permissions; } - public UpdateGroupCanEditInfoChanged() { + public UpdateGroupPermissionsChanged() { } @@ -38,27 +38,27 @@ public int getGroupId() { return this.groupId; } - public boolean canEditGroup() { - return this.canEditGroup; + public long getPermissions() { + return this.permissions; } @Override public void parse(BserValues values) throws IOException { this.groupId = values.getInt(1); - this.canEditGroup = values.getBool(2); + this.permissions = values.getLong(2); } @Override public void serialize(BserWriter writer) throws IOException { writer.writeInt(1, this.groupId); - writer.writeBool(2, this.canEditGroup); + writer.writeLong(2, this.permissions); } @Override public String toString() { - String res = "update GroupCanEditInfoChanged{"; + String res = "update GroupPermissionsChanged{"; res += "groupId=" + this.groupId; - res += ", canEditGroup=" + this.canEditGroup; + res += ", permissions=" + this.permissions; res += "}"; return res; } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Group.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Group.java index e6e40e1bf7..288fdab82c 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Group.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Group.java @@ -17,8 +17,11 @@ import im.actor.core.api.ApiFullUser; import im.actor.core.api.ApiGroup; import im.actor.core.api.ApiGroupFull; +import im.actor.core.api.ApiGroupFullPermissions; +import im.actor.core.api.ApiGroupPermissions; import im.actor.core.api.ApiMapValue; import im.actor.core.api.ApiMember; +import im.actor.core.util.BitMaskUtil; import im.actor.runtime.bser.BserCreator; import im.actor.runtime.bser.BserValues; import im.actor.runtime.bser.BserWriter; @@ -52,7 +55,13 @@ public class Group extends WrapperExtEntity implements K @Property("readonly, nonatomic") private boolean isMember; @Property("readonly, nonatomic") - private boolean canWrite; + private boolean isCanSendMessage; + @Property("readonly, nonatomic") + private boolean isCanClear; + @Property("readonly, nonatomic") + private boolean isCanLeave; + @Property("readonly, nonatomic") + private boolean isCanDelete; @NotNull @Property("readonly, nonatomic") @SuppressWarnings("NullableProblems") @@ -84,6 +93,8 @@ public class Group extends WrapperExtEntity implements K @Property("readonly, nonatomic") private boolean isCanInviteViaLink; @Property("readonly, nonatomic") + private boolean isCanCall; + @Property("readonly, nonatomic") private boolean isCanViewMembers; @Property("readonly, nonatomic") private boolean isSharedHistory; @@ -97,10 +108,6 @@ public class Group extends WrapperExtEntity implements K private boolean isCanViewAdmins; @Property("readonly, nonatomic") private boolean isCanEditAdmins; - @Property("readonly, nonatomic") - private boolean isCanLeave; - @Property("readonly, nonatomic") - private boolean isCanDelete; @Property("readonly, nonatomic") private boolean haveExtension; @@ -155,8 +162,24 @@ public boolean isMember() { return isMember; } - public boolean isCanWrite() { - return canWrite; + public boolean isCanSendMessage() { + return isCanSendMessage; + } + + public boolean isCanClear() { + return isCanClear; + } + + public boolean isCanLeave() { + return isCanLeave; + } + + public boolean isCanDelete() { + return isCanDelete; + } + + public boolean isCanCall() { + return isCanCall; } @NotNull @@ -237,14 +260,6 @@ public boolean isCanInviteViaLink() { return isCanInviteViaLink; } - public boolean isCanLeave() { - return isCanLeave; - } - - public boolean isCanDelete() { - return isCanDelete; - } - public Group updateExt(@Nullable ApiGroupFull ext) { return new Group(getWrapped(), ext); } @@ -264,7 +279,7 @@ public Group editTitle(String title) { w.isMember(), w.isHidden(), w.getGroupType(), - w.canSendMessage(), + w.getPermissions(), w.isDeleted(), w.getExt()); res.setUnmappedObjects(w.getUnmappedObjects()); @@ -282,7 +297,7 @@ public Group editAvatar(ApiAvatar avatar) { w.isMember(), w.isHidden(), w.getGroupType(), - w.canSendMessage(), + w.getPermissions(), w.isDeleted(), w.getExt()); res.setUnmappedObjects(w.getUnmappedObjects()); @@ -300,14 +315,14 @@ public Group editIsMember(boolean isMember) { isMember, w.isHidden(), w.getGroupType(), - w.canSendMessage(), + w.getPermissions(), w.isDeleted(), w.getExt()); res.setUnmappedObjects(w.getUnmappedObjects()); return new Group(res, getWrappedExt()); } - public Group editExt(ApiMapValue ext) { + public Group editIsDeleted(boolean isDeleted) { ApiGroup w = getWrapped(); ApiGroup res = new ApiGroup( w.getId(), @@ -318,14 +333,14 @@ public Group editExt(ApiMapValue ext) { w.isMember(), w.isHidden(), w.getGroupType(), - w.canSendMessage(), - w.isDeleted(), - ext); + w.getPermissions(), + isDeleted, + w.getExt()); res.setUnmappedObjects(w.getUnmappedObjects()); return new Group(res, getWrappedExt()); } - public Group editCanWrite(boolean canWrite) { + public Group editPermissions(long permissions) { ApiGroup w = getWrapped(); ApiGroup res = new ApiGroup( w.getId(), @@ -336,13 +351,32 @@ public Group editCanWrite(boolean canWrite) { w.isMember(), w.isHidden(), w.getGroupType(), - canWrite, + permissions, w.isDeleted(), w.getExt()); res.setUnmappedObjects(w.getUnmappedObjects()); return new Group(res, getWrappedExt()); } + public Group editExt(ApiMapValue ext) { + ApiGroup w = getWrapped(); + ApiGroup res = new ApiGroup( + w.getId(), + w.getAccessHash(), + w.getTitle(), + w.getAvatar(), + w.getMembersCount(), + w.isMember(), + w.isHidden(), + w.getGroupType(), + w.getPermissions(), + w.isDeleted(), + ext); + res.setUnmappedObjects(w.getUnmappedObjects()); + return new Group(res, getWrappedExt()); + } + + // // Members // @@ -359,18 +393,9 @@ public Group editMembers(List members) { e.getAbout(), e.getExt(), e.isAsyncMembers(), - e.canViewMembers(), - e.canInvitePeople(), e.isSharedHistory(), - e.canEditGroupInfo(), e.getShortName(), - e.canEditShortName(), - e.canEditAdminList(), - e.canViewAdminList(), - e.canEditAdminSettings(), - e.canInviteViaLink(), - e.canDelete(), - e.canLeave()); + e.getPermissions()); fullExt.setUnmappedObjects(e.getUnmappedObjects()); } @@ -384,7 +409,7 @@ public Group editMembers(List members) { w.isMember(), w.isHidden(), w.getGroupType(), - w.canSendMessage(), + w.getPermissions(), w.isDeleted(), w.getExt()); @@ -427,18 +452,9 @@ public Group editMembers(List added, List removed, int count e.getAbout(), e.getExt(), e.isAsyncMembers(), - e.canViewMembers(), - e.canInvitePeople(), e.isSharedHistory(), - e.canEditGroupInfo(), e.getShortName(), - e.canEditShortName(), - e.canEditAdminList(), - e.canViewAdminList(), - e.canEditAdminSettings(), - e.canInviteViaLink(), - e.canDelete(), - e.canLeave()); + e.getPermissions()); fullExt.setUnmappedObjects(e.getUnmappedObjects()); } @@ -452,7 +468,7 @@ public Group editMembers(List added, List removed, int count w.isMember(), w.isHidden(), w.getGroupType(), - w.canSendMessage(), + w.getPermissions(), w.isDeleted(), w.getExt()); @@ -470,7 +486,7 @@ public Group editMembersCount(int membersCount) { w.isMember(), w.isHidden(), w.getGroupType(), - w.canSendMessage(), + w.getPermissions(), w.isDeleted(), w.getExt()); res.setUnmappedObjects(w.getUnmappedObjects()); @@ -488,18 +504,9 @@ public Group editMembersBecameAsync() { e.getAbout(), e.getExt(), true, - e.canViewMembers(), - e.canInvitePeople(), e.isSharedHistory(), - e.canEditGroupInfo(), e.getShortName(), - e.canEditShortName(), - e.canEditAdminList(), - e.canViewAdminList(), - e.canEditAdminSettings(), - e.canInviteViaLink(), - e.canDelete(), - e.canLeave()); + e.getPermissions()); fullExt.setUnmappedObjects(e.getUnmappedObjects()); return new Group(getWrapped(), fullExt); @@ -531,18 +538,9 @@ public Group editMemberChangedAdmin(int uid, Boolean isAdmin) { e.getAbout(), e.getExt(), e.isAsyncMembers(), - e.canViewMembers(), - e.canInvitePeople(), e.isSharedHistory(), - e.canEditGroupInfo(), e.getShortName(), - e.canEditShortName(), - e.canEditAdminList(), - e.canViewAdminList(), - e.canEditAdminSettings(), - e.canInviteViaLink(), - e.canDelete(), - e.canLeave()); + e.getPermissions()); fullExt.setUnmappedObjects(e.getUnmappedObjects()); return new Group(getWrapped(), fullExt); @@ -566,18 +564,9 @@ public Group editTopic(String topic) { e.getAbout(), e.getExt(), e.isAsyncMembers(), - e.canViewMembers(), - e.canInvitePeople(), e.isSharedHistory(), - e.canEditGroupInfo(), e.getShortName(), - e.canEditShortName(), - e.canEditAdminList(), - e.canViewAdminList(), - e.canEditAdminSettings(), - e.canInviteViaLink(), - e.canDelete(), - e.canLeave()); + e.getPermissions()); fullExt.setUnmappedObjects(e.getUnmappedObjects()); return new Group(getWrapped(), fullExt); } else { @@ -596,18 +585,9 @@ public Group editAbout(String about) { about, e.getExt(), e.isAsyncMembers(), - e.canViewMembers(), - e.canInvitePeople(), e.isSharedHistory(), - e.canEditGroupInfo(), e.getShortName(), - e.canEditShortName(), - e.canEditAdminList(), - e.canViewAdminList(), - e.canEditAdminSettings(), - e.canInviteViaLink(), - e.canDelete(), - e.canLeave()); + e.getPermissions()); fullExt.setUnmappedObjects(e.getUnmappedObjects()); return new Group(getWrapped(), fullExt); } else { @@ -626,18 +606,9 @@ public Group editShortName(String shortName) { e.getAbout(), e.getExt(), e.isAsyncMembers(), - e.canViewMembers(), - e.canInvitePeople(), e.isSharedHistory(), - e.canEditGroupInfo(), shortName, - e.canEditShortName(), - e.canEditAdminList(), - e.canViewAdminList(), - e.canEditAdminSettings(), - e.canInviteViaLink(), - e.canDelete(), - e.canLeave()); + e.getPermissions()); fullExt.setUnmappedObjects(e.getUnmappedObjects()); return new Group(getWrapped(), fullExt); } else { @@ -656,48 +627,9 @@ public Group editFullExt(ApiMapValue ext) { e.getAbout(), ext, e.isAsyncMembers(), - e.canViewMembers(), - e.canInvitePeople(), - e.isSharedHistory(), - e.canEditGroupInfo(), - e.getShortName(), - e.canEditShortName(), - e.canEditAdminList(), - e.canViewAdminList(), - e.canEditAdminSettings(), - e.canInviteViaLink(), - e.canDelete(), - e.canLeave()); - fullExt.setUnmappedObjects(e.getUnmappedObjects()); - return new Group(getWrapped(), fullExt); - } else { - return this; - } - } - - public Group editCanViewMembers(boolean canViewMembers) { - if (getWrappedExt() != null) { - ApiGroupFull e = getWrappedExt(); - ApiGroupFull fullExt = new ApiGroupFull(e.getId(), - e.getCreateDate(), - e.getOwnerUid(), - e.getMembers(), - e.getTheme(), - e.getAbout(), - e.getExt(), - e.isAsyncMembers(), - canViewMembers, - e.canInvitePeople(), e.isSharedHistory(), - e.canEditGroupInfo(), e.getShortName(), - e.canEditShortName(), - e.canEditAdminList(), - e.canViewAdminList(), - e.canEditAdminSettings(), - e.canInviteViaLink(), - e.canDelete(), - e.canLeave()); + e.getPermissions()); fullExt.setUnmappedObjects(e.getUnmappedObjects()); return new Group(getWrapped(), fullExt); } else { @@ -705,209 +637,20 @@ public Group editCanViewMembers(boolean canViewMembers) { } } - public Group editCanInviteViaLink(boolean canInviteViaLink) { - if (getWrappedExt() != null) { - ApiGroupFull e = getWrappedExt(); - ApiGroupFull fullExt = new ApiGroupFull(e.getId(), - e.getCreateDate(), - e.getOwnerUid(), - e.getMembers(), - e.getTheme(), - e.getAbout(), - e.getExt(), - e.isAsyncMembers(), - e.canViewMembers(), - e.canInvitePeople(), - e.isSharedHistory(), - e.canEditGroupInfo(), - e.getShortName(), - e.canEditShortName(), - e.canEditAdminList(), - e.canViewAdminList(), - e.canEditAdminSettings(), - canInviteViaLink, - e.canDelete(), - e.canLeave()); - fullExt.setUnmappedObjects(e.getUnmappedObjects()); - return new Group(getWrapped(), fullExt); - } else { - return this; - } - } - - public Group editCanDelete(boolean canDelete) { - if (getWrappedExt() != null) { - ApiGroupFull e = getWrappedExt(); - ApiGroupFull fullExt = new ApiGroupFull(e.getId(), - e.getCreateDate(), - e.getOwnerUid(), - e.getMembers(), - e.getTheme(), - e.getAbout(), - e.getExt(), - e.isAsyncMembers(), - e.canViewMembers(), - e.canInvitePeople(), - e.isSharedHistory(), - e.canEditGroupInfo(), - e.getShortName(), - e.canEditShortName(), - e.canEditAdminList(), - e.canViewAdminList(), - e.canEditAdminSettings(), - e.canInviteViaLink(), - canDelete, - e.canLeave()); - fullExt.setUnmappedObjects(e.getUnmappedObjects()); - return new Group(getWrapped(), fullExt); - } else { - return this; - } - } - - public Group editCanLeave(boolean canLeave) { - if (getWrappedExt() != null) { - ApiGroupFull e = getWrappedExt(); - ApiGroupFull fullExt = new ApiGroupFull(e.getId(), - e.getCreateDate(), - e.getOwnerUid(), - e.getMembers(), - e.getTheme(), - e.getAbout(), - e.getExt(), - e.isAsyncMembers(), - e.canViewMembers(), - e.canInvitePeople(), - e.isSharedHistory(), - e.canEditGroupInfo(), - e.getShortName(), - e.canEditShortName(), - e.canEditAdminList(), - e.canViewAdminList(), - e.canEditAdminSettings(), - e.canInviteViaLink(), - e.canDelete(), - canLeave); - fullExt.setUnmappedObjects(e.getUnmappedObjects()); - return new Group(getWrapped(), fullExt); - } else { - return this; - } - } - - public Group editCanEditGroupInfo(boolean canEditGroupInfo) { - if (getWrappedExt() != null) { - ApiGroupFull e = getWrappedExt(); - ApiGroupFull fullExt = new ApiGroupFull(e.getId(), - e.getCreateDate(), - e.getOwnerUid(), - e.getMembers(), - e.getTheme(), - e.getAbout(), - e.getExt(), - e.isAsyncMembers(), - e.canViewMembers(), - e.canInvitePeople(), - e.isSharedHistory(), - canEditGroupInfo, - e.getShortName(), - e.canEditShortName(), - e.canEditAdminList(), - e.canViewAdminList(), - e.canEditAdminSettings(), - e.canInviteViaLink(), - e.canDelete(), - e.canLeave()); - fullExt.setUnmappedObjects(e.getUnmappedObjects()); - return new Group(getWrapped(), fullExt); - } else { - return this; - } - } - - public Group editCanEditShortName(boolean canEditShortName) { - if (getWrappedExt() != null) { - ApiGroupFull e = getWrappedExt(); - ApiGroupFull fullExt = new ApiGroupFull(e.getId(), - e.getCreateDate(), - e.getOwnerUid(), - e.getMembers(), - e.getTheme(), - e.getAbout(), - e.getExt(), - e.isAsyncMembers(), - e.canViewMembers(), - e.canInvitePeople(), - e.isSharedHistory(), - e.canEditGroupInfo(), - e.getShortName(), - canEditShortName, - e.canEditAdminList(), - e.canViewAdminList(), - e.canEditAdminSettings(), - e.canInviteViaLink(), - e.canDelete(), - e.canLeave()); - fullExt.setUnmappedObjects(e.getUnmappedObjects()); - return new Group(getWrapped(), fullExt); - } else { - return this; - } - } - - public Group editCanEditAdminList(boolean canEditAdminList) { - if (getWrappedExt() != null) { - ApiGroupFull e = getWrappedExt(); - ApiGroupFull fullExt = new ApiGroupFull(e.getId(), - e.getCreateDate(), - e.getOwnerUid(), - e.getMembers(), - e.getTheme(), - e.getAbout(), - e.getExt(), - e.isAsyncMembers(), - e.canViewMembers(), - e.canInvitePeople(), - e.isSharedHistory(), - e.canEditGroupInfo(), - e.getShortName(), - e.canEditShortName(), - canEditAdminList, - e.canViewAdminList(), - e.canEditAdminSettings(), - e.canInviteViaLink(), - e.canDelete(), - e.canLeave()); - fullExt.setUnmappedObjects(e.getUnmappedObjects()); - return new Group(getWrapped(), fullExt); - } else { - return this; - } - } - - public Group editCanViewAdminList(boolean canViewAdminList) { + public Group editOwner(int uid) { if (getWrappedExt() != null) { ApiGroupFull e = getWrappedExt(); ApiGroupFull fullExt = new ApiGroupFull(e.getId(), e.getCreateDate(), - e.getOwnerUid(), + uid, e.getMembers(), e.getTheme(), e.getAbout(), e.getExt(), e.isAsyncMembers(), - e.canViewMembers(), - e.canInvitePeople(), e.isSharedHistory(), - e.canEditGroupInfo(), e.getShortName(), - e.canEditShortName(), - e.canEditAdminList(), - canViewAdminList, - e.canEditAdminSettings(), - e.canInviteViaLink(), - e.canDelete(), - e.canLeave()); + e.getPermissions()); fullExt.setUnmappedObjects(e.getUnmappedObjects()); return new Group(getWrapped(), fullExt); } else { @@ -915,7 +658,7 @@ public Group editCanViewAdminList(boolean canViewAdminList) { } } - public Group editCanEditAdminSettings(boolean canEditAdminSettings) { + public Group editHistoryShared() { if (getWrappedExt() != null) { ApiGroupFull e = getWrappedExt(); ApiGroupFull fullExt = new ApiGroupFull(e.getId(), @@ -926,18 +669,9 @@ public Group editCanEditAdminSettings(boolean canEditAdminSettings) { e.getAbout(), e.getExt(), e.isAsyncMembers(), - e.canViewMembers(), - e.canInvitePeople(), - e.isSharedHistory(), - e.canEditGroupInfo(), + true, e.getShortName(), - e.canEditShortName(), - e.canEditAdminList(), - e.canViewAdminList(), - canEditAdminSettings, - e.canInviteViaLink(), - e.canDelete(), - e.canLeave()); + e.getPermissions()); fullExt.setUnmappedObjects(e.getUnmappedObjects()); return new Group(getWrapped(), fullExt); } else { @@ -945,7 +679,7 @@ public Group editCanEditAdminSettings(boolean canEditAdminSettings) { } } - public Group editCanInviteMembers(boolean canInviteMembers) { + public Group editExtPermissions(long permissions) { if (getWrappedExt() != null) { ApiGroupFull e = getWrappedExt(); ApiGroupFull fullExt = new ApiGroupFull(e.getId(), @@ -956,48 +690,9 @@ public Group editCanInviteMembers(boolean canInviteMembers) { e.getAbout(), e.getExt(), e.isAsyncMembers(), - e.canViewMembers(), - canInviteMembers, - e.isSharedHistory(), - e.canEditGroupInfo(), - e.getShortName(), - e.canEditShortName(), - e.canEditAdminList(), - e.canViewAdminList(), - e.canEditAdminSettings(), - e.canInviteViaLink(), - e.canDelete(), - e.canLeave()); - fullExt.setUnmappedObjects(e.getUnmappedObjects()); - return new Group(getWrapped(), fullExt); - } else { - return this; - } - } - - public Group editOwner(int uid) { - if (getWrappedExt() != null) { - ApiGroupFull e = getWrappedExt(); - ApiGroupFull fullExt = new ApiGroupFull(e.getId(), - e.getCreateDate(), - uid, - e.getMembers(), - e.getTheme(), - e.getAbout(), - e.getExt(), - e.isAsyncMembers(), - e.canViewMembers(), - e.canInvitePeople(), e.isSharedHistory(), - e.canEditGroupInfo(), e.getShortName(), - e.canEditShortName(), - e.canEditAdminList(), - e.canViewAdminList(), - e.canEditAdminSettings(), - e.canInviteViaLink(), - e.canDelete(), - e.canLeave()); + permissions); fullExt.setUnmappedObjects(e.getUnmappedObjects()); return new Group(getWrapped(), fullExt); } else { @@ -1005,37 +700,6 @@ public Group editOwner(int uid) { } } - public Group editHistoryShared() { - if (getWrappedExt() != null) { - ApiGroupFull e = getWrappedExt(); - ApiGroupFull fullExt = new ApiGroupFull(e.getId(), - e.getCreateDate(), - e.getOwnerUid(), - e.getMembers(), - e.getTheme(), - e.getAbout(), - e.getExt(), - e.isAsyncMembers(), - e.canViewMembers(), - e.canInvitePeople(), - true, - e.canEditGroupInfo(), - e.getShortName(), - e.canEditShortName(), - e.canEditAdminList(), - e.canViewAdminList(), - e.canEditAdminSettings(), - e.canInviteViaLink(), - e.canDelete(), - e.canLeave()); - fullExt.setUnmappedObjects(e.getUnmappedObjects()); - return new Group(getWrapped(), fullExt); - } else { - return this; - } - } - - @Override protected void applyWrapped(@NotNull ApiGroup wrapped, @Nullable ApiGroupFull ext) { @@ -1064,12 +728,20 @@ protected void applyWrapped(@NotNull ApiGroup wrapped, @Nullable ApiGroupFull ex } } - if (wrapped.canSendMessage() != null) { - this.canWrite = wrapped.canSendMessage(); - } else { - // True is default for groups and false otherwise - this.canWrite = this.groupType == GroupType.GROUP; + long permissions = 0; + if (wrapped.getPermissions() != null) { + permissions = wrapped.getPermissions(); } + /* + # 0 - canSendMessage. Default is FALSE. + # 1 - canClear. Default is FALSE. + # 2 - canLeave. Default is FALSE. + # 3 - canDelete. Default is FALSE. + */ + this.isCanSendMessage = BitMaskUtil.getBitValue(permissions, ApiGroupPermissions.SEND_MESSAGE); + this.isCanClear = BitMaskUtil.getBitValue(permissions, ApiGroupPermissions.CLEAR); + this.isCanLeave = BitMaskUtil.getBitValue(permissions, ApiGroupPermissions.LEAVE); + this.isCanDelete = BitMaskUtil.getBitValue(permissions, ApiGroupPermissions.DELETE); // // Ext @@ -1082,17 +754,30 @@ protected void applyWrapped(@NotNull ApiGroup wrapped, @Nullable ApiGroupFull ex this.topic = ext.getTheme(); this.shortName = ext.getShortName(); this.isAsyncMembers = ext.isAsyncMembers() != null ? ext.isAsyncMembers() : false; - this.isCanViewMembers = ext.canViewMembers() != null ? ext.canViewMembers() : true; - this.isCanInviteMembers = ext.canViewMembers() != null ? ext.canViewMembers() : true; - this.isCanInviteViaLink = ext.canInviteViaLink() != null ? ext.canInviteViaLink() : false; this.isSharedHistory = ext.isSharedHistory() != null ? ext.isSharedHistory() : false; - this.isCanEditInfo = ext.canEditGroupInfo() != null ? ext.canEditGroupInfo() : false; - this.isCanEditShortName = ext.canEditShortName() != null ? ext.canEditShortName() : false; - this.isCanEditAdministration = ext.canEditAdminSettings() != null ? ext.canEditAdminSettings() : false; - this.isCanViewAdmins = ext.canViewAdminList() != null ? ext.canViewAdminList() : false; - this.isCanEditAdmins = ext.canEditAdminList() != null ? ext.canEditAdminList() : false; - this.isCanLeave = ext.canLeave() != null ? ext.canLeave() : true; - this.isCanDelete = ext.canDelete() != null ? ext.canDelete() : false; + + /* + # 0 - canEditInfo. Default is FALSE. + # 1 - canViewMembers. Default is FALSE. + # 2 - canInviteMembers. Default is FALSE. + # 3 - canInviteViaLink. Default is FALSE. + # 4 - canCall. Default is FALSE. + # 5 - canEditAdminSettings. Default is FALSE. + # 6 - canViewAdmins. Default is FALSE. + # 7 - canEditAdmins. Default is FALSE. + */ + long fullPermissions = 0; + if (ext.getPermissions() != null) { + fullPermissions = ext.getPermissions(); + } + this.isCanEditInfo = BitMaskUtil.getBitValue(fullPermissions, ApiGroupFullPermissions.EDIT_INFO); + this.isCanViewMembers = BitMaskUtil.getBitValue(fullPermissions, ApiGroupFullPermissions.VIEW_MEMBERS); + this.isCanInviteMembers = BitMaskUtil.getBitValue(fullPermissions, ApiGroupFullPermissions.INVITE_MEMBERS); + this.isCanInviteViaLink = BitMaskUtil.getBitValue(fullPermissions, ApiGroupFullPermissions.INVITE_VIA_LINK); + this.isCanCall = BitMaskUtil.getBitValue(fullPermissions, ApiGroupFullPermissions.CALL); + this.isCanEditAdministration = BitMaskUtil.getBitValue(fullPermissions, ApiGroupFullPermissions.EDIT_ADMIN_SETTINGS); + this.isCanViewAdmins = BitMaskUtil.getBitValue(fullPermissions, ApiGroupFullPermissions.VIEW_ADMINS); + this.isCanEditAdmins = BitMaskUtil.getBitValue(fullPermissions, ApiGroupFullPermissions.EDIT_ADMINS); this.members = new ArrayList<>(); for (ApiMember m : ext.getMembers()) { @@ -1115,8 +800,6 @@ protected void applyWrapped(@NotNull ApiGroup wrapped, @Nullable ApiGroupFull ex this.isCanEditAdministration = false; this.isCanViewAdmins = false; this.isCanEditAdmins = false; - this.isCanDelete = false; - this.isCanLeave = false; this.isCanInviteViaLink = false; } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsProcessor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsProcessor.java index 18559a6fef..08f844cd52 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsProcessor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsProcessor.java @@ -6,18 +6,9 @@ import im.actor.core.api.updates.UpdateGroupAboutChanged; import im.actor.core.api.updates.UpdateGroupAvatarChanged; -import im.actor.core.api.updates.UpdateGroupCanDeleteChanged; -import im.actor.core.api.updates.UpdateGroupCanEditAdminsChanged; -import im.actor.core.api.updates.UpdateGroupCanEditInfoChanged; -import im.actor.core.api.updates.UpdateGroupCanEditUsernameChanged; -import im.actor.core.api.updates.UpdateGroupCanInviteMembersChanged; -import im.actor.core.api.updates.UpdateGroupCanInviteViaLink; -import im.actor.core.api.updates.UpdateGroupCanLeaveChanged; -import im.actor.core.api.updates.UpdateGroupCanSendMessagesChanged; -import im.actor.core.api.updates.UpdateGroupCanViewAdminsChanged; -import im.actor.core.api.updates.UpdateGroupCanViewMembersChanged; import im.actor.core.api.updates.UpdateGroupExtChanged; import im.actor.core.api.updates.UpdateGroupFullExtChanged; +import im.actor.core.api.updates.UpdateGroupFullPermissionsChanged; import im.actor.core.api.updates.UpdateGroupHistoryShared; import im.actor.core.api.updates.UpdateGroupMemberAdminChanged; import im.actor.core.api.updates.UpdateGroupMemberChanged; @@ -26,6 +17,7 @@ import im.actor.core.api.updates.UpdateGroupMembersCountChanged; import im.actor.core.api.updates.UpdateGroupMembersUpdated; import im.actor.core.api.updates.UpdateGroupOwnerChanged; +import im.actor.core.api.updates.UpdateGroupPermissionsChanged; import im.actor.core.api.updates.UpdateGroupShortNameChanged; import im.actor.core.api.updates.UpdateGroupTitleChanged; import im.actor.core.api.updates.UpdateGroupTopicChanged; @@ -45,9 +37,9 @@ public GroupsProcessor(ModuleContext context) { @Override public Promise process(Update update) { if (update instanceof UpdateGroupTitleChanged || - update instanceof UpdateGroupCanSendMessagesChanged || update instanceof UpdateGroupMemberChanged || update instanceof UpdateGroupAvatarChanged || + update instanceof UpdateGroupPermissionsChanged || update instanceof UpdateGroupExtChanged || update instanceof UpdateGroupMembersUpdated || @@ -59,19 +51,10 @@ public Promise process(Update update) { update instanceof UpdateGroupShortNameChanged || update instanceof UpdateGroupAboutChanged || update instanceof UpdateGroupTopicChanged || - update instanceof UpdateGroupFullExtChanged || update instanceof UpdateGroupOwnerChanged || update instanceof UpdateGroupHistoryShared || - - update instanceof UpdateGroupCanViewMembersChanged || - update instanceof UpdateGroupCanInviteMembersChanged || - update instanceof UpdateGroupCanEditInfoChanged || - update instanceof UpdateGroupCanViewAdminsChanged || - update instanceof UpdateGroupCanEditAdminsChanged || - update instanceof UpdateGroupCanEditUsernameChanged || - update instanceof UpdateGroupCanInviteViaLink || - update instanceof UpdateGroupCanLeaveChanged || - update instanceof UpdateGroupCanDeleteChanged) { + update instanceof UpdateGroupFullPermissionsChanged || + update instanceof UpdateGroupFullExtChanged) { return context().getGroupsModule().getRouter().onUpdate(update); } return null; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/router/GroupRouter.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/router/GroupRouter.java index 0d76dc6375..9861166624 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/router/GroupRouter.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/router/GroupRouter.java @@ -14,19 +14,9 @@ import im.actor.core.api.rpc.RequestLoadFullGroups; import im.actor.core.api.updates.UpdateGroupAboutChanged; import im.actor.core.api.updates.UpdateGroupAvatarChanged; -import im.actor.core.api.updates.UpdateGroupCanDeleteChanged; -import im.actor.core.api.updates.UpdateGroupCanEditAdminSettingsChanged; -import im.actor.core.api.updates.UpdateGroupCanEditAdminsChanged; -import im.actor.core.api.updates.UpdateGroupCanEditInfoChanged; -import im.actor.core.api.updates.UpdateGroupCanEditUsernameChanged; -import im.actor.core.api.updates.UpdateGroupCanInviteMembersChanged; -import im.actor.core.api.updates.UpdateGroupCanInviteViaLink; -import im.actor.core.api.updates.UpdateGroupCanLeaveChanged; -import im.actor.core.api.updates.UpdateGroupCanSendMessagesChanged; -import im.actor.core.api.updates.UpdateGroupCanViewAdminsChanged; -import im.actor.core.api.updates.UpdateGroupCanViewMembersChanged; import im.actor.core.api.updates.UpdateGroupExtChanged; import im.actor.core.api.updates.UpdateGroupFullExtChanged; +import im.actor.core.api.updates.UpdateGroupFullPermissionsChanged; import im.actor.core.api.updates.UpdateGroupHistoryShared; import im.actor.core.api.updates.UpdateGroupMemberAdminChanged; import im.actor.core.api.updates.UpdateGroupMemberChanged; @@ -35,6 +25,7 @@ import im.actor.core.api.updates.UpdateGroupMembersCountChanged; import im.actor.core.api.updates.UpdateGroupMembersUpdated; import im.actor.core.api.updates.UpdateGroupOwnerChanged; +import im.actor.core.api.updates.UpdateGroupPermissionsChanged; import im.actor.core.api.updates.UpdateGroupShortNameChanged; import im.actor.core.api.updates.UpdateGroupTitleChanged; import im.actor.core.api.updates.UpdateGroupTopicChanged; @@ -81,13 +72,13 @@ public Promise onTitleChanged(int groupId, String title) { } @Verified - public Promise onCanWriteMessagesChanged(int groupId, boolean canWrite) { - return editGroup(groupId, group -> group.editCanWrite(canWrite)); + public Promise onIsMemberChanged(int groupId, boolean isMember) { + return editGroup(groupId, group -> group.editIsMember(isMember)); } @Verified - public Promise onIsMemberChanged(int groupId, boolean isMember) { - return editGroup(groupId, group -> group.editIsMember(isMember)); + public Promise onPermissionsChanged(int groupId, long permissions) { + return editGroup(groupId, group -> group.editPermissions(permissions)); } @Verified @@ -143,69 +134,25 @@ public Promise onShortNameChanged(int groupId, String shortName) { return editGroup(groupId, group -> group.editShortName(shortName)); } - @Verified - public Promise onFullExtChanged(int groupId, ApiMapValue ext) { - return editGroup(groupId, group -> group.editFullExt(ext)); - } - - @Verified - public Promise onEditCanViewMembers(int groupId, boolean canViewMembers) { - return editGroup(groupId, group -> group.editCanViewMembers(canViewMembers)); - } - - @Verified - public Promise onEditCanInviteMembers(int groupId, boolean canViewMembers) { - return editGroup(groupId, group -> group.editCanInviteMembers(canViewMembers)); - } @Verified - public Promise onEditCanEditGroupInfo(int groupId, boolean canEditGroupInfo) { - return editGroup(groupId, group -> group.editCanEditGroupInfo(canEditGroupInfo)); - } - - @Verified - public Promise onEditCanEditShortName(int groupId, boolean canEditShortName) { - return editGroup(groupId, group -> group.editCanEditShortName(canEditShortName)); - } - - @Verified - public Promise onEditCanEditAdminList(int groupId, boolean canEditAminList) { - return editGroup(groupId, group -> group.editCanEditAdminList(canEditAminList)); - } - - @Verified - public Promise onEditCanViewAdminList(int groupId, boolean canViewAdminList) { - return editGroup(groupId, group -> group.editCanViewAdminList(canViewAdminList)); - } - - @Verified - public Promise onEditCanEditAdminSettings(int groupId, boolean canEditAdminSettings) { - return editGroup(groupId, group -> group.editCanEditAdminSettings(canEditAdminSettings)); - } - - @Verified - public Promise onEditCanLeaveChanged(int groupId, boolean canLeave) { - return editGroup(groupId, group -> group.editCanLeave(canLeave)); - } - - @Verified - public Promise onEditCanDeleteChanged(int groupId, boolean canDelete) { - return editGroup(groupId, group -> group.editCanLeave(canDelete)); + public Promise onOwnerChanged(int groupId, int updatedOwner) { + return editGroup(groupId, group -> group.editOwner(updatedOwner)); } @Verified - public Promise onEditCanInviteViaLinkChanged(int groupId, boolean canInvite) { - return editGroup(groupId, group -> group.editCanInviteViaLink(canInvite)); + public Promise onHistoryShared(int groupId) { + return editGroup(groupId, group -> group.editHistoryShared()); } @Verified - public Promise onOwnerChanged(int groupId, int updatedOwner) { - return editGroup(groupId, group -> group.editOwner(updatedOwner)); + public Promise onFullPermissionsChanged(int groupId, long permissions) { + return editGroup(groupId, group -> group.editExtPermissions(permissions)); } @Verified - public Promise onHistoryShared(int groupId) { - return editGroup(groupId, group -> group.editHistoryShared()); + public Promise onFullExtChanged(int groupId, ApiMapValue ext) { + return editGroup(groupId, group -> group.editFullExt(ext)); } // @@ -348,6 +295,9 @@ private Promise onUpdate(Update update) { } else if (update instanceof UpdateGroupMemberChanged) { UpdateGroupMemberChanged memberChanged = (UpdateGroupMemberChanged) update; return onIsMemberChanged(memberChanged.getGroupId(), memberChanged.isMember()); + } else if (update instanceof UpdateGroupPermissionsChanged) { + UpdateGroupPermissionsChanged permissionsChanged = (UpdateGroupPermissionsChanged) update; + return onPermissionsChanged(permissionsChanged.getGroupId(), permissionsChanged.getPermissions()); } else if (update instanceof UpdateGroupExtChanged) { UpdateGroupExtChanged extChanged = (UpdateGroupExtChanged) update; return onExtChanged(extChanged.getGroupId(), extChanged.getExt()); @@ -392,51 +342,15 @@ else if (update instanceof UpdateGroupTopicChanged) { } else if (update instanceof UpdateGroupOwnerChanged) { UpdateGroupOwnerChanged ownerChanged = (UpdateGroupOwnerChanged) update; return onOwnerChanged(ownerChanged.getGroupId(), ownerChanged.getUserId()); - } else if (update instanceof UpdateGroupFullExtChanged) { - UpdateGroupFullExtChanged extChanged = (UpdateGroupFullExtChanged) update; - return onFullExtChanged(extChanged.getGroupId(), extChanged.getExt()); } else if (update instanceof UpdateGroupShortNameChanged) { UpdateGroupShortNameChanged shortNameChanged = (UpdateGroupShortNameChanged) update; return onShortNameChanged(shortNameChanged.getGroupId(), shortNameChanged.getShortName()); - } - - // - // Actions - // - - else if (update instanceof UpdateGroupCanSendMessagesChanged) { - UpdateGroupCanSendMessagesChanged messagesChanged = (UpdateGroupCanSendMessagesChanged) update; - return onCanWriteMessagesChanged(messagesChanged.getGroupId(), messagesChanged.canSendMessages()); - } else if (update instanceof UpdateGroupCanViewMembersChanged) { - UpdateGroupCanViewMembersChanged membersChanged = (UpdateGroupCanViewMembersChanged) update; - return onEditCanViewMembers(membersChanged.getGroupId(), membersChanged.canViewMembers()); - } else if (update instanceof UpdateGroupCanInviteMembersChanged) { - UpdateGroupCanInviteMembersChanged changed = (UpdateGroupCanInviteMembersChanged) update; - return onEditCanInviteMembers(changed.getGroupId(), changed.canInviteMembers()); - } else if (update instanceof UpdateGroupCanEditInfoChanged) { - UpdateGroupCanEditInfoChanged editInfoChanged = (UpdateGroupCanEditInfoChanged) update; - return onEditCanEditGroupInfo(editInfoChanged.getGroupId(), editInfoChanged.canEditGroup()); - } else if (update instanceof UpdateGroupCanEditUsernameChanged) { - UpdateGroupCanEditUsernameChanged shortName = (UpdateGroupCanEditUsernameChanged) update; - return onEditCanEditShortName(shortName.getGroupId(), shortName.canEditUsername()); - } else if (update instanceof UpdateGroupCanViewAdminsChanged) { - UpdateGroupCanViewAdminsChanged changed = (UpdateGroupCanViewAdminsChanged) update; - return onEditCanViewAdminList(changed.getGroupId(), changed.canViewAdmins()); - } else if (update instanceof UpdateGroupCanEditAdminsChanged) { - UpdateGroupCanEditAdminsChanged editAdmins = (UpdateGroupCanEditAdminsChanged) update; - return onEditCanEditAdminList(editAdmins.getGroupId(), editAdmins.canAssignAdmins()); - } else if (update instanceof UpdateGroupCanEditAdminSettingsChanged) { - UpdateGroupCanEditAdminSettingsChanged settings = (UpdateGroupCanEditAdminSettingsChanged) update; - return onEditCanEditAdminSettings(settings.getGroupId(), settings.canEditAdminSettings()); - } else if (update instanceof UpdateGroupCanLeaveChanged) { - UpdateGroupCanLeaveChanged canLeaveChanged = (UpdateGroupCanLeaveChanged) update; - return onEditCanLeaveChanged(canLeaveChanged.getGroupId(), canLeaveChanged.canLeaveChanged()); - } else if (update instanceof UpdateGroupCanDeleteChanged) { - UpdateGroupCanDeleteChanged canDeleteChanged = (UpdateGroupCanDeleteChanged) update; - return onEditCanDeleteChanged(canDeleteChanged.getGroupId(), canDeleteChanged.canDeleteChanged()); - } else if (update instanceof UpdateGroupCanInviteViaLink) { - UpdateGroupCanInviteViaLink inviteViaLink = (UpdateGroupCanInviteViaLink) update; - return onEditCanInviteViaLinkChanged(inviteViaLink.getGroupId(), inviteViaLink.canInviteViaLink()); + } else if (update instanceof UpdateGroupFullPermissionsChanged) { + UpdateGroupFullPermissionsChanged permissionsChanged = (UpdateGroupFullPermissionsChanged) update; + return onFullPermissionsChanged(permissionsChanged.getGroupId(), permissionsChanged.getPermissions()); + } else if (update instanceof UpdateGroupFullExtChanged) { + UpdateGroupFullExtChanged extChanged = (UpdateGroupFullExtChanged) update; + return onFullExtChanged(extChanged.getGroupId(), extChanged.getExt()); } return Promise.success(null); diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/util/BitMaskUtil.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/util/BitMaskUtil.java new file mode 100644 index 0000000000..860bd12d26 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/util/BitMaskUtil.java @@ -0,0 +1,21 @@ +package im.actor.core.util; + +public class BitMaskUtil { + + public static boolean getBitValue(long src, int index) { + return getBitValue(src, index, false); + } + + public static boolean getBitValue(long src, Enum e) { + return getBitValue(src, e.ordinal(), false); + } + + public static boolean getBitValue(long src, int index, boolean def) { + int val = (int) ((src >> index) & 1); + if (val == 0) { + return def; + } else { + return !def; + } + } +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java index 1a76dadb54..ef0b04dff1 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java @@ -53,6 +53,9 @@ public class GroupVM extends BaseValueModel { @NotNull @Property("nonatomic, readonly") private BooleanValueModel isCanWriteMessage; + @NotNull + @Property("nonatomic, readonly") + private BooleanValueModel isCanCall; @NotNull @Property("nonatomic, readonly") @@ -126,8 +129,8 @@ public GroupVM(@NotNull Group rawObj) { this.avatar = new AvatarValueModel("group." + groupId + ".avatar", rawObj.getAvatar()); this.isMember = new BooleanValueModel("group." + groupId + ".isMember", rawObj.isMember()); this.membersCount = new IntValueModel("group." + groupId + ".membersCount", rawObj.getMembersCount()); - this.isCanWriteMessage = new BooleanValueModel("group." + groupId + ".can_write", rawObj.isCanWrite()); - + this.isCanWriteMessage = new BooleanValueModel("group." + groupId + ".can_write", rawObj.isCanSendMessage()); + this.isCanCall = new BooleanValueModel("group." + groupId + ".can_call", rawObj.isCanCall()); this.isCanViewMembers = new BooleanValueModel("group." + groupId + ".can_view_members", rawObj.isCanViewMembers()); this.isCanInviteMembers = new BooleanValueModel("group." + groupId + ".can_invite_members", rawObj.isCanInviteMembers()); this.isCanEditInfo = new BooleanValueModel("group." + groupId + ".can_edit_info", rawObj.isCanEditInfo()); @@ -358,6 +361,17 @@ public BooleanValueModel getIsCanDelete() { return isCanDelete; } + /** + * Is current user can call in this group + * + * @return is current user can call model + */ + @NotNull + @ObjectiveCName("getIsCanCallModel") + public BooleanValueModel getIsCanCall() { + return isCanCall; + } + /** * Is current user can invite via link * @@ -456,7 +470,7 @@ protected void updateValues(@NotNull Group rawObj) { isChanged |= avatar.change(rawObj.getAvatar()); isChanged |= membersCount.change(rawObj.getMembersCount()); isChanged |= isMember.change(rawObj.isMember()); - isChanged |= isCanWriteMessage.change(rawObj.isCanWrite()); + isChanged |= isCanWriteMessage.change(rawObj.isCanSendMessage()); isChanged |= theme.change(rawObj.getTopic()); isChanged |= about.change(rawObj.getAbout()); From c11e97e450531751e2a2487aa3b68e955974914e Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Thu, 21 Jul 2016 21:55:24 +0300 Subject: [PATCH 091/414] ref(core): Improve initial state handling --- .../modules/contacts/BookImportActor.java | 2 + .../messaging/dialogs/DialogsActor.java | 1 - .../core/modules/misc/AppStateModule.java | 17 ++-- .../core/modules/misc/ListsStatesActor.java | 88 ------------------- 4 files changed, 7 insertions(+), 101 deletions(-) delete mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/misc/ListsStatesActor.java diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/contacts/BookImportActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/contacts/BookImportActor.java index 2618f6bd11..c5418335a1 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/contacts/BookImportActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/contacts/BookImportActor.java @@ -78,6 +78,8 @@ public void preStart() { private void performSync() { // Ignoring syncing if not enabled if (!config().isEnablePhoneBookImport()) { + // Marking as everything is imported + context().getAppStateModule().onBookImported(); return; } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/dialogs/DialogsActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/dialogs/DialogsActor.java index bde055b7ac..994c545acc 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/dialogs/DialogsActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/dialogs/DialogsActor.java @@ -301,7 +301,6 @@ private Promise onHistoryLoaded(List history) { } addOrUpdateItems(updated); updateSearch(updated); - context().getAppStateModule().onDialogsLoaded(); notifyState(true); return Promise.success(null); } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/misc/AppStateModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/misc/AppStateModule.java index eba6428c2e..fdc14ed2ee 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/misc/AppStateModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/misc/AppStateModule.java @@ -4,21 +4,15 @@ package im.actor.core.modules.misc; -import im.actor.core.api.ApiAppCounters; import im.actor.core.modules.AbsModule; import im.actor.core.modules.ModuleContext; import im.actor.core.viewmodel.AppStateVM; import im.actor.core.viewmodel.GlobalStateVM; -import im.actor.runtime.actors.ActorCreator; -import im.actor.runtime.actors.ActorRef; - -import static im.actor.runtime.actors.ActorSystem.system; public class AppStateModule extends AbsModule { private AppStateVM appStateVM; private GlobalStateVM globalStateVM; - private ActorRef listStatesActor; public AppStateModule(ModuleContext context) { super(context); @@ -28,27 +22,26 @@ public AppStateModule(ModuleContext context) { public void run() { this.appStateVM = new AppStateVM(context()); - listStatesActor = system().actorOf("actor/app/state", () -> new ListsStatesActor(context())); } public void onDialogsUpdate(boolean isEmpty) { - listStatesActor.send(new ListsStatesActor.OnDialogsChanged(isEmpty)); + appStateVM.onDialogsChanged(isEmpty); } public void onContactsUpdate(boolean isEmpty) { - listStatesActor.send(new ListsStatesActor.OnContactsChanged(isEmpty)); + appStateVM.onContactsChanged(isEmpty); } public void onBookImported() { - listStatesActor.send(new ListsStatesActor.OnBookImported()); + appStateVM.onPhoneImported(); } public void onContactsLoaded() { - listStatesActor.send(new ListsStatesActor.OnContactsLoaded()); + appStateVM.onContactsLoaded(); } public void onDialogsLoaded() { - listStatesActor.send(new ListsStatesActor.OnDialogsLoaded()); + appStateVM.onDialogsLoaded(); } public AppStateVM getAppStateVM() { diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/misc/ListsStatesActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/misc/ListsStatesActor.java deleted file mode 100644 index 79926383fd..0000000000 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/misc/ListsStatesActor.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2015 Actor LLC. - */ - -package im.actor.core.modules.misc; - -import im.actor.core.modules.ModuleContext; -import im.actor.core.modules.ModuleActor; - -public class ListsStatesActor extends ModuleActor { - - public ListsStatesActor(ModuleContext context) { - super(context); - } - - public void onDialogsChanged(boolean isEmpty) { - context().getAppStateModule().getAppStateVM().onDialogsChanged(isEmpty); - } - - public void onContactsChanged(boolean isEmpty) { - context().getAppStateModule().getAppStateVM().onContactsChanged(isEmpty); - } - - public void onBookImported() { - context().getAppStateModule().getAppStateVM().onPhoneImported(); - } - - public void onContactsLoaded() { - context().getAppStateModule().getAppStateVM().onContactsLoaded(); - } - - public void onDialogsLoaded() { - context().getAppStateModule().getAppStateVM().onDialogsLoaded(); - } - - @Override - public void onReceive(Object message) { - if (message instanceof OnContactsChanged) { - onContactsChanged(((OnContactsChanged) message).isEmpty()); - } else if (message instanceof OnDialogsChanged) { - onDialogsChanged(((OnDialogsChanged) message).isEmpty()); - } else if (message instanceof OnBookImported) { - onBookImported(); - } else if (message instanceof OnContactsLoaded) { - onContactsLoaded(); - } else if (message instanceof OnDialogsLoaded) { - onDialogsLoaded(); - } else { - super.onReceive(message); - } - } - - public static class OnBookImported { - - } - - public static class OnContactsLoaded { - - } - - public static class OnDialogsLoaded { - - } - - public static class OnContactsChanged { - private boolean isEmpty; - - public OnContactsChanged(boolean isEmpty) { - this.isEmpty = isEmpty; - } - - public boolean isEmpty() { - return isEmpty; - } - } - - public static class OnDialogsChanged { - private boolean isEmpty; - - public OnDialogsChanged(boolean isEmpty) { - this.isEmpty = isEmpty; - } - - public boolean isEmpty() { - return isEmpty; - } - } -} From 3f528fc1b91fd07a8fc5518079f3a911745cb37e Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Thu, 21 Jul 2016 22:52:58 +0300 Subject: [PATCH 092/414] fix(iOS): hidden input bar --- .../Controllers/Conversation/ConversationViewController.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Conversation/ConversationViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Conversation/ConversationViewController.swift index 2db42f9b86..2a0f889832 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Conversation/ConversationViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Conversation/ConversationViewController.swift @@ -335,6 +335,8 @@ public class ConversationViewController: } } }) + + self.inputOverlay.hidden = true } else if (peer.peerType.ordinal() == ACPeerType.GROUP().ordinal()) { let group = Actor.getGroupWithGid(peer.peerId) let nameModel = group.getNameModel() From 7f5f16c850a69d86997246b309d7837eab0a67e6 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Thu, 21 Jul 2016 23:19:18 +0300 Subject: [PATCH 093/414] fix(core+android): Remove canEditShortName permission --- .../controllers/group/GroupAdminFragment.java | 2 +- .../sdk/controllers/group/GroupInfoFragment.java | 3 +-- .../main/java/im/actor/core/entity/Group.java | 7 ------- .../java/im/actor/core/viewmodel/GroupVM.java | 16 ---------------- 4 files changed, 2 insertions(+), 26 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupAdminFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupAdminFragment.java index 2e3b26ddd6..6c9d14477e 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupAdminFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupAdminFragment.java @@ -67,7 +67,7 @@ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, } else { groupTypeValue.setText(R.string.group_type_pubic); } - if (groupVM.getIsCanEditShortName().get()) { + if (groupVM.getIsCanEditAdministration().get()) { res.findViewById(R.id.groupTypeContainer).setOnClickListener(v -> { startActivity(new Intent(getContext(), GroupTypeActivity.class) .putExtra(Intents.EXTRA_GROUP_ID, groupVM.getId())); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java index 9cedd34e4b..bd715fbeb7 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java @@ -217,8 +217,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa }); // Administration - if (groupVM.getIsCanEditAdministration().get() || - groupVM.getIsCanEditShortName().get()) { + if (groupVM.getIsCanEditAdministration().get()) { administrationAction.setOnClickListener(view -> { startActivity(new Intent(getActivity(), GroupAdminActivity.class) .putExtra(Intents.EXTRA_GROUP_ID, chatId)); diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Group.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Group.java index 288fdab82c..5b417d601b 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Group.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Group.java @@ -103,8 +103,6 @@ public class Group extends WrapperExtEntity implements K @Property("readonly, nonatomic") private boolean isCanEditAdministration; @Property("readonly, nonatomic") - private boolean isCanEditShortName; - @Property("readonly, nonatomic") private boolean isCanViewAdmins; @Property("readonly, nonatomic") private boolean isCanEditAdmins; @@ -240,10 +238,6 @@ public boolean isHaveExtension() { return haveExtension; } - public boolean isCanEditShortName() { - return isCanEditShortName; - } - public boolean isCanEditAdministration() { return isCanEditAdministration; } @@ -796,7 +790,6 @@ protected void applyWrapped(@NotNull ApiGroup wrapped, @Nullable ApiGroupFull ex this.isCanInviteMembers = false; this.isSharedHistory = false; this.isCanEditInfo = false; - this.isCanEditShortName = false; this.isCanEditAdministration = false; this.isCanViewAdmins = false; this.isCanEditAdmins = false; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java index ef0b04dff1..11e9ba0fe6 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java @@ -80,9 +80,6 @@ public class GroupVM extends BaseValueModel { private BooleanValueModel isCanEditAdministration; @NotNull @Property("nonatomic, readonly") - private BooleanValueModel isCanEditShortName; - @NotNull - @Property("nonatomic, readonly") private BooleanValueModel isCanEditAdmins; @NotNull @Property("nonatomic, readonly") @@ -136,7 +133,6 @@ public GroupVM(@NotNull Group rawObj) { this.isCanEditInfo = new BooleanValueModel("group." + groupId + ".can_edit_info", rawObj.isCanEditInfo()); this.isAsyncMembers = new BooleanValueModel("group." + groupId + ".isAsyncMembers", rawObj.isAsyncMembers()); this.isCanEditAdministration = new BooleanValueModel("group." + groupId + ".isCanEditAdministration", rawObj.isCanEditAdministration()); - this.isCanEditShortName = new BooleanValueModel("group." + groupId + ".isCanEditShortName", rawObj.isCanEditShortName()); this.isHistoryShared = new BooleanValueModel("group." + groupId + ".isHistoryShared", rawObj.isSharedHistory()); this.isCanEditAdmins = new BooleanValueModel("group." + groupId + ".isCanEditAdmins", rawObj.isCanEditAdmins()); this.isCanViewAdmins = new BooleanValueModel("group." + groupId + ".isCanViewAdmins", rawObj.isCanViewAdmins()); @@ -328,17 +324,6 @@ public BooleanValueModel getIsCanEditAdministration() { return isCanEditAdministration; } - /** - * Is current user can edit short name of a group - * - * @return is current user can edit short name - */ - @NotNull - @ObjectiveCName("getIsCanEditShortNameModel") - public BooleanValueModel getIsCanEditShortName() { - return isCanEditShortName; - } - /** * Is current user can leave group * @@ -479,7 +464,6 @@ protected void updateValues(@NotNull Group rawObj) { isChanged |= isCanViewMembers.change(rawObj.isCanViewMembers()); isChanged |= isCanInviteMembers.change(rawObj.isCanInviteMembers()); isChanged |= isCanEditInfo.change(rawObj.isCanEditInfo()); - isChanged |= isCanEditShortName.change(rawObj.isCanEditShortName()); isChanged |= shortName.change(rawObj.getShortName()); isChanged |= isAsyncMembers.change(rawObj.isAsyncMembers()); isChanged |= isHistoryShared.change(rawObj.isSharedHistory()); From f60a797fed3ebb492b00388ad33598914ea82510 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Thu, 21 Jul 2016 23:28:58 +0300 Subject: [PATCH 094/414] fix(android): search results loadRequiredPeers --- .../sdk/controllers/search/GlobalSearchBaseFragment.java | 7 ++++--- .../java/im/actor/core/modules/search/SearchModule.java | 2 ++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/search/GlobalSearchBaseFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/search/GlobalSearchBaseFragment.java index 879d4a1db3..eea5c5871d 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/search/GlobalSearchBaseFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/search/GlobalSearchBaseFragment.java @@ -171,14 +171,15 @@ public void onResult(List res) { Avatar avatar = null; if (res.size() > 0) { peer = res.get(0).getPeer(); - + name = res.get(0).getOptMatchString(); + if (name == null) { + return; + } if (peer.getPeerType() == PeerType.PRIVATE) { UserVM userVM = users().get(peer.getPeerId()); - name = userVM.getName().getName(); avatar = userVM.getAvatar().get(); } else if (peer.getPeerType() == PeerType.GROUP) { GroupVM groupVM = groups().get(peer.getPeerId()); - name = groupVM.getName().getName(); avatar = groupVM.getAvatar().get(); } else { return; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/search/SearchModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/search/SearchModule.java index 2497c4e743..5dff5fe657 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/search/SearchModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/search/SearchModule.java @@ -127,6 +127,8 @@ public Promise> findPeers(ArrayList c updates().applyRelatedData( responsePeerSearch.getUsers(), responsePeerSearch.getGroups())) + .chain(responsePeerSearch2 -> + updates().loadRequiredPeers(responsePeerSearch2.getUserPeers(), responsePeerSearch2.getGroupPeers())) .map(responsePeerSearch1 -> ManagedList.of(responsePeerSearch1.getSearchResults()) .map(r -> new PeerSearchEntity(convert(r.getPeer()), r.getOptMatchString()))); From 8d0012d20e8bec6acca4d6bb2477097433615e3c Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Thu, 21 Jul 2016 23:32:21 +0300 Subject: [PATCH 095/414] feat+fix(iOS): Localize Mute/Unmute, removed using isCanEditShortName --- .../actor/sdk/controllers/group/GroupInfoFragment.java | 2 +- .../ActorSDK/Resources/Base.lproj/Localizable.strings | 4 ++++ .../ActorSDK/Resources/es.lproj/Localizable.strings | 4 ++++ .../ActorSDK/Resources/pt.lproj/Localizable.strings | 4 ++++ .../ActorSDK/Resources/ru.lproj/Localizable.strings | 4 ++++ .../Resources/zh-Hans.lproj/Localizable.strings | 4 ++++ .../Conversation/ConversationViewController.swift | 10 +++++----- .../Group/AAGroupAdministrationViewController.swift | 2 +- .../Controllers/Group/AAGroupViewController.swift | 2 +- 9 files changed, 28 insertions(+), 8 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java index bd715fbeb7..a323e15cec 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java @@ -217,7 +217,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa }); // Administration - if (groupVM.getIsCanEditAdministration().get()) { + if (groupVM.getIsCanEditAdministration().get() || groupVM.getIsCanDelete().get()) { administrationAction.setOnClickListener(view -> { startActivity(new Intent(getActivity(), GroupAdminActivity.class) .putExtra(Intents.EXTRA_GROUP_ID, chatId)); diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/Base.lproj/Localizable.strings b/actor-sdk/sdk-core-ios/ActorSDK/Resources/Base.lproj/Localizable.strings index 7a77f90cc8..1e2e716f68 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Resources/Base.lproj/Localizable.strings +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/Base.lproj/Localizable.strings @@ -635,6 +635,10 @@ "ActionOpenCode" = "View Code"; +"ActionMute" = "Mute"; + +"ActionUnmute" = "Unmute"; + /* * Network */ diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/es.lproj/Localizable.strings b/actor-sdk/sdk-core-ios/ActorSDK/Resources/es.lproj/Localizable.strings index 286771cab2..9cea8f6ec7 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Resources/es.lproj/Localizable.strings +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/es.lproj/Localizable.strings @@ -625,6 +625,10 @@ "ActionOpenCode" = "View Code"; +"ActionMute" = "Mute"; + +"ActionUnmute" = "Unmute"; + /* * Network */ diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/pt.lproj/Localizable.strings b/actor-sdk/sdk-core-ios/ActorSDK/Resources/pt.lproj/Localizable.strings index 55547b8930..e338c3018d 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Resources/pt.lproj/Localizable.strings +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/pt.lproj/Localizable.strings @@ -608,6 +608,10 @@ "ActionOpenCode" = "View Code"; +"ActionMute" = "Mute"; + +"ActionUnmute" = "Unmute"; + /* * Network */ diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/ru.lproj/Localizable.strings b/actor-sdk/sdk-core-ios/ActorSDK/Resources/ru.lproj/Localizable.strings index 163d7f2de1..01d6c34a3e 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Resources/ru.lproj/Localizable.strings +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/ru.lproj/Localizable.strings @@ -618,6 +618,10 @@ "ActionOpenCode" = "Посмотреть код"; +"ActionMute" = "Заглушить"; + +"ActionUnmute" = "Включить оповещения"; + /* * Network */ diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/zh-Hans.lproj/Localizable.strings b/actor-sdk/sdk-core-ios/ActorSDK/Resources/zh-Hans.lproj/Localizable.strings index d113b29052..cc0b4e05a8 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Resources/zh-Hans.lproj/Localizable.strings +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/zh-Hans.lproj/Localizable.strings @@ -613,6 +613,10 @@ "ActionOpenCode" = "查看代码"; +"ActionMute" = "Mute"; + +"ActionUnmute" = "Unmute"; + /* * Network */ diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Conversation/ConversationViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Conversation/ConversationViewController.swift index 2a0f889832..9ec90e563d 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Conversation/ConversationViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Conversation/ConversationViewController.swift @@ -385,12 +385,12 @@ public class ConversationViewController: self.inputOverlay.hidden = true } else { if !isMember.booleanValue() { - self.inputOverlayLabel.text = "Not a member" + self.inputOverlayLabel.text = AALocalized("ChatNoGroupAccess") } else { if Actor.isNotificationsEnabledWithPeer(self.peer) { - self.inputOverlayLabel.text = "Mute" + self.inputOverlayLabel.text = AALocalized("ActionMute") } else { - self.inputOverlayLabel.text = "Unmute" + self.inputOverlayLabel.text = AALocalized("ActionUnmute") } } self.stickersButton.hidden = true @@ -421,10 +421,10 @@ public class ConversationViewController: } else if !group.isCanWriteMessage.get().booleanValue() { if Actor.isNotificationsEnabledWithPeer(peer) { Actor.changeNotificationsEnabledWithPeer(peer, withValue: false) - inputOverlayLabel.text = "Unmute" + inputOverlayLabel.text = AALocalized("ActionUnmute") } else { Actor.changeNotificationsEnabledWithPeer(peer, withValue: true) - inputOverlayLabel.text = "Mute" + inputOverlayLabel.text = AALocalized("ActionMute") } } } else if peer.isPrivate { diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupAdministrationViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupAdministrationViewController.swift index 4f93446357..954fd3eadb 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupAdministrationViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupAdministrationViewController.swift @@ -40,7 +40,7 @@ public class AAGroupAdministrationViewController: AAContentTableController { } } - if group.isCanEditShortName.get().booleanValue() { + if group.isCanEditAdministration.get().booleanValue() { r.style = .Navigation r.selectAction = { () -> Bool in self.navigateNext(AAGroupTypeViewController(gid: self.gid)) diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewController.swift index b6ae21c0d5..bf39264f62 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewController.swift @@ -144,7 +144,7 @@ public class AAGroupViewController: AAContentTableController { } // Admininstration - if group.isCanEditShortName.get().booleanValue() || group.isCanDelete.get().booleanValue() { + if group.isCanEditAdministration.get().booleanValue() || group.isCanDelete.get().booleanValue() { s.common({ (r) in r.content = AALocalized("GroupAdministration") r.selectAction = { () -> Bool in From 13b5f5c2ed89e5ede90007845fdb63be98eaddba Mon Sep 17 00:00:00 2001 From: kor_ka Date: Thu, 21 Jul 2016 23:50:23 +0300 Subject: [PATCH 096/414] fix(android): search results name from VM if no opt string --- .../search/GlobalSearchBaseFragment.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/search/GlobalSearchBaseFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/search/GlobalSearchBaseFragment.java index eea5c5871d..6df76f80e9 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/search/GlobalSearchBaseFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/search/GlobalSearchBaseFragment.java @@ -170,16 +170,16 @@ public void onResult(List res) { String name = null; Avatar avatar = null; if (res.size() > 0) { - peer = res.get(0).getPeer(); - name = res.get(0).getOptMatchString(); - if (name == null) { - return; - } + PeerSearchEntity peerSearchEntity = res.get(0); + peer = peerSearchEntity.getPeer(); + if (peer.getPeerType() == PeerType.PRIVATE) { UserVM userVM = users().get(peer.getPeerId()); + name = userVM.getName().get(); avatar = userVM.getAvatar().get(); } else if (peer.getPeerType() == PeerType.GROUP) { GroupVM groupVM = groups().get(peer.getPeerId()); + name = groupVM.getName().get(); avatar = groupVM.getAvatar().get(); } else { return; @@ -190,6 +190,10 @@ public void onResult(List res) { showResult = false; break; } + + if (peerSearchEntity.getOptMatchString() != null) { + name = peerSearchEntity.getOptMatchString(); + } } if (showResult) { footerSearchHolder.bind(new SearchEntity(peer, 0, avatar, name), activeSearchQuery, true); From 4424e4bdba4c198090c837172615804fd1394586 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Fri, 22 Jul 2016 01:00:59 +0300 Subject: [PATCH 097/414] feat(iOS): Channel Creation --- .../Resources/Base.lproj/Localizable.strings | 15 ++++++- .../ic_create_channel.imageset/Contents.json | 23 ++++++++++ .../Megaphone Filled-32.png | Bin 0 -> 419 bytes .../Megaphone Filled-64.png | Bin 0 -> 855 bytes .../Megaphone Filled-96.png | Bin 0 -> 1282 bytes .../Resources/es.lproj/Localizable.strings | 11 +++++ .../Resources/pt.lproj/Localizable.strings | 12 ++++++ .../Resources/ru.lproj/Localizable.strings | 11 +++++ .../zh-Hans.lproj/Localizable.strings | 11 +++++ .../Compose/AAComposeController.swift | 16 ++++++- .../Compose/AAGroupCreateViewController.swift | 40 ++++++++++++------ .../Compose/AAGroupMembersController.swift | 26 ++++++------ .../AAGroupAdministrationViewController.swift | 23 ++++++++-- .../Group/AAGroupTypeController.swift | 32 ++++++++++++-- .../Group/AAGroupViewController.swift | 2 +- 15 files changed, 185 insertions(+), 37 deletions(-) create mode 100644 actor-sdk/sdk-core-ios/ActorSDK/Resources/Images.xcassets/ic_create_channel.imageset/Contents.json create mode 100644 actor-sdk/sdk-core-ios/ActorSDK/Resources/Images.xcassets/ic_create_channel.imageset/Megaphone Filled-32.png create mode 100644 actor-sdk/sdk-core-ios/ActorSDK/Resources/Images.xcassets/ic_create_channel.imageset/Megaphone Filled-64.png create mode 100644 actor-sdk/sdk-core-ios/ActorSDK/Resources/Images.xcassets/ic_create_channel.imageset/Megaphone Filled-96.png diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/Base.lproj/Localizable.strings b/actor-sdk/sdk-core-ios/ActorSDK/Resources/Base.lproj/Localizable.strings index 1e2e716f68..848b3e9449 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Resources/Base.lproj/Localizable.strings +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/Base.lproj/Localizable.strings @@ -372,13 +372,24 @@ "ComposeTitle" = "New Message"; + "CreateGroup" = "Create Group"; +"CreateChannel" = "Create Channel"; + + "CreateGroupTitle" = "Create Group"; -"CreateGroupHint" = "Please provide the group's subject and an optional group icon"; +"CreateGroupHint" = "Please provide the group name and an optional group icon"; + +"CreateGroupNamePlaceholder" = "Group Name"; + +"CreateChannelTitle" = "Create Channel"; + +"CreateChannelHint" = "Please provide the channel name and an optional channel icon"; + +"CreateChannelNamePlaceholder" = "Channel Name"; -"CreateGroupNamePlaceholder" = "Group Subject"; "CreateGroupMembersTitle" = "Invite members"; diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/Images.xcassets/ic_create_channel.imageset/Contents.json b/actor-sdk/sdk-core-ios/ActorSDK/Resources/Images.xcassets/ic_create_channel.imageset/Contents.json new file mode 100644 index 0000000000..63a34293ca --- /dev/null +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/Images.xcassets/ic_create_channel.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Megaphone Filled-32.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "Megaphone Filled-64.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "Megaphone Filled-96.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/Images.xcassets/ic_create_channel.imageset/Megaphone Filled-32.png b/actor-sdk/sdk-core-ios/ActorSDK/Resources/Images.xcassets/ic_create_channel.imageset/Megaphone Filled-32.png new file mode 100644 index 0000000000000000000000000000000000000000..d63f1a9413d85d53d295ce86aa13f9f9aa8f95eb GIT binary patch literal 419 zcmV;U0bKrxP)}Hpo60W9iReqKnHZ71SL?xna{j6PbQJ%C6}0SJCm8aB=`2) zfA{UaTQIXNX7ZoDyIkcm!WW-*6H{cv#^dj5^ zj)9}(w7SXG0-&^K$*G@zvrz!*r0v>HiUHcB9Ry%R=1qIZR_Y_|Ab`9oZ-EON1hCb0 z)gJHFreKvV@REutzt>~^)>k(G8wF|=u28XJ*~#;xZS<~@hV zIWQJXQ761g5Go_kPXEBae^CTrqjHX$D$Ne5DkU&Q06I5%m8hCaSDnd7te|TVu8rLPTvxkDQdES z5#F6N7+}B#_)<#feENP&c`6Qg({rEFIq9*ys@`mWOn!jOk%GSk@CiE4RLlpZPj>(S N002ovPDHLkV1n~;u{Zz# literal 0 HcmV?d00001 diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/Images.xcassets/ic_create_channel.imageset/Megaphone Filled-64.png b/actor-sdk/sdk-core-ios/ActorSDK/Resources/Images.xcassets/ic_create_channel.imageset/Megaphone Filled-64.png new file mode 100644 index 0000000000000000000000000000000000000000..477f8a90eb3ea53ef8d270aee36836902fc5f9f6 GIT binary patch literal 855 zcmV-d1E~CoP)Iv0CYeq2vh(a02RQzjCMw2BwMn4a!!(F zGMBltZ0*zE)vl})b>2=ow_gLmP(bGdh6{8qfbN3eJxMnteV6o`$?wGlF#MCGJ5KQ! z|3=bz4*+gT`XPzWX?xlO0A~7iB-UND2;i}#Q%Pse=+Bb)F4$TCfL}k5^u+yP=!K+* z&f>4`_oe_4)R(A!(=PZAS}WN!m>U$dPsv07|5Nv;ek} z_E7;igtV_U0CH{L=uqyRYC!3A)m8zdO&0b%l8}@dvbKLV00PrHX9T4p>Eo-&)0t(G z5wftQgk-G&rTwx1V8mshTm-@+ZzL2K&mv`I>43NHp9Ns9T6!VrLrPJxwFQOu%L4Fg zW>!Bs|7JY1RaN1Dx9*+=;P1@SlW(-O1%>xZ9yj{|1He!~oItXa3MIRB_vCR87Z@>M z1i{%=;ET7@+H4;1P14;+6ng7kAvK){y>x}pl))cY9Q|xP6~?~l>m!V(#HCKKM)k*c z(YGvHuVn@I%UTBQIaGzz_mso9#ntQMKx3l=7$_&^It63jr0XgGYn}`=gK>M!4Gz?& z8UPzV`w6HA*FiaZ!>9@X74ZI@cJ5pQ7{m+Iu?LK*0Q>-Y-^CAK*=(N~434MNXjK7N zf`t5+c7p8{HlK%tu?qnIp-Z!DE6Mw%3s?SL6@b5|cxz_)oZ5m4IJiV(>y5av)v{U@ z0RC+=2@Ez;V>yHmAq38rRqj~=qbdN5@(U$WXN+T3NvJ~2Upf4O%fFUuNVe9jB-H`n z(?FQcS_lS)A$>dA!n+6h&b8cSwK@PBJkpJ&$iTog{4+iNwtH&4OIcQT17Jp^q{wTG z4+TcAuXSw>mHvs>^8US+i)8e&50WHpI hj!i=WIkZ~5_8-S1);ITVqZR-F002ovPDHLkV1nR%f{p+H literal 0 HcmV?d00001 diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/Images.xcassets/ic_create_channel.imageset/Megaphone Filled-96.png b/actor-sdk/sdk-core-ios/ActorSDK/Resources/Images.xcassets/ic_create_channel.imageset/Megaphone Filled-96.png new file mode 100644 index 0000000000000000000000000000000000000000..4f1516e6904cb585982826718df821b2aae70499 GIT binary patch literal 1282 zcmV+d1^xPoP);q&dixGHC5oOY3;0B2%vEzAOHertU%-H=>Y`LSb@gX(*p>gu>y^&rw0%~ zV+9&lPYSYw@UyTT67j_nGjh8D{Z$M02*4*$}MZ@rG;Eu z-p~Mg4lPz7Ljf2mwCo1JsG+490E7j^;BR4?jo(Yt9zu)tnu~=XjH_{sA{}vj6p@GV zS(eN<+hb_i6u>JHd3vNqoMpKaydpLOv`2%DTCBoa?c5%KaD!tMa|xbso*3aXgqu@N zZU7&`)ZQW@ya31RG!a07<>=oQ<%7+OaIB61{)y)H00LOh0yCxr;M32Yn*iJr`U)Tb zpMLJ#1mKp?R{#O{^mFGX0Jns`0tmpTpF1}JxFz%zKmb1d+_?$BEupUf0`TeQ&P@Pr z34H~)0dP6GF#w#yd2jd*-!r%Aa6h2HegL$88@3jwIQ$CRj`3y;Yzd$c0EjuYFN~HC zFGvF^cy!yDzb4|LjXYQk8kzI z0WS<;HQ|m%Kmv@>!c_MF(&y*oCUkM5)sfUFFuDhjK0V~{s4ioe&=A%0u(o>u=~`17 zek?3{o?~;LP5EL!g zli&qEk^o)H1m)7Dfn0hnZ}XG8$?v^AAX04AC0 s84*A|ZA~Q;fJvr$Mg&k#TT{t@03sqi5U9nI*Z=?k07*qoM6N<$g7&v7egFUf literal 0 HcmV?d00001 diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/es.lproj/Localizable.strings b/actor-sdk/sdk-core-ios/ActorSDK/Resources/es.lproj/Localizable.strings index 9cea8f6ec7..f255c963bd 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Resources/es.lproj/Localizable.strings +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/es.lproj/Localizable.strings @@ -367,14 +367,25 @@ "ComposeTitle" = "Nuevo mensaje"; + "CreateGroup" = "Crear grupo"; +"CreateChannel" = "Create channel"; + + "CreateGroupTitle" = "Crear grupo"; "CreateGroupHint" = "Por favor, introduce un nombre de grupo e inserta una imagen."; "CreateGroupNamePlaceholder" = "Grupo Asunto"; +"CreateChannelTitle" = "Create Channel"; + +"CreateChannelHint" = "Please provide the channel name and an optional channel icon"; + +"CreateChannelNamePlaceholder" = "Channel Name"; + + "CreateGroupMembersTitle" = "Invitar a los miembros"; "CreateGroupMembersPlaceholders" = "Introduce Nombres"; diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/pt.lproj/Localizable.strings b/actor-sdk/sdk-core-ios/ActorSDK/Resources/pt.lproj/Localizable.strings index e338c3018d..297a9dbe26 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Resources/pt.lproj/Localizable.strings +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/pt.lproj/Localizable.strings @@ -356,14 +356,26 @@ "ComposeTitle" = "Nova Mensagem"; + "CreateGroup" = "Criar Grupo"; +"CreateChannel" = "Create Channel"; + + "CreateGroupTitle" = "Criar Grupo"; "CreateGroupNamePlaceholder" = "Inserir nome do grupo"; "CreateGroupHint" = "Please provide group subject and optional group icon"; + +"CreateChannelTitle" = "Create Channel"; + +"CreateChannelHint" = "Please provide the channel name and an optional channel icon"; + +"CreateChannelNamePlaceholder" = "Channel Name"; + + "CreateGroupMembersTitle" = "Invite members"; "CreateGroupMembersPlaceholders" = "Enter Names"; diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/ru.lproj/Localizable.strings b/actor-sdk/sdk-core-ios/ActorSDK/Resources/ru.lproj/Localizable.strings index 01d6c34a3e..1036d7bfa1 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Resources/ru.lproj/Localizable.strings +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/ru.lproj/Localizable.strings @@ -372,12 +372,23 @@ "CreateGroup" = "Новая группа"; +"CreateChannel" = "Новый канал"; + + "CreateGroupTitle" = "Новая группа"; "CreateGroupNamePlaceholder" = "Название группы"; "CreateGroupHint" = "Укажите название группы и поставьте фотографию"; + +"CreateChannelTitle" = "Новый канал"; + +"CreateChannelHint" = "Укажите название канала и поставьте фотографию"; + +"CreateChannelNamePlaceholder" = "Название канала"; + + "CreateGroupMembersTitle" = "Пригласить участников"; "CreateGroupMembersPlaceholders" = "Введите имена"; diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/zh-Hans.lproj/Localizable.strings b/actor-sdk/sdk-core-ios/ActorSDK/Resources/zh-Hans.lproj/Localizable.strings index cc0b4e05a8..258aa99fa4 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Resources/zh-Hans.lproj/Localizable.strings +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/zh-Hans.lproj/Localizable.strings @@ -352,14 +352,25 @@ "ComposeTitle" = "新消息"; + "CreateGroup" = "创建群组"; +"CreateChannel" = "Create Channel"; + + "CreateGroupTitle" = "创建群组"; "CreateGroupHint" = "请填写群组名称和图标"; "CreateGroupNamePlaceholder" = "输入群组名称"; +"CreateChannelTitle" = "Create Channel"; + +"CreateChannelHint" = "Please provide the channel name and an optional channel icon"; + +"CreateChannelNamePlaceholder" = "Channel Name"; + + "CreateGroupMembersTitle" = "邀请成员"; "CreateGroupMembersPlaceholders" = "输入名称"; diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Compose/AAComposeController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Compose/AAComposeController.swift index 3149a5a6eb..5640467db6 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Compose/AAComposeController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Compose/AAComposeController.swift @@ -33,7 +33,21 @@ public class AAComposeController: AAContactsListContentController, AAContactsLis } r.selectAction = { () -> Bool in - self.navigateNext(AAGroupCreateViewController(), removeCurrent: true) + self.navigateNext(AAGroupCreateViewController(isChannel: false), removeCurrent: true) + return false + } + } + + section.custom { (r:AACustomRow) -> () in + + r.height = 56 + + r.closure = { (cell) -> () in + cell.bind("ic_create_channel", actionTitle: AALocalized("CreateChannel")) + } + + r.selectAction = { () -> Bool in + self.navigateNext(AAGroupCreateViewController(isChannel: true), removeCurrent: true) return false } } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Compose/AAGroupCreateViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Compose/AAGroupCreateViewController.swift index cd16ca6f6b..379fc5cef1 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Compose/AAGroupCreateViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Compose/AAGroupCreateViewController.swift @@ -6,6 +6,7 @@ import Foundation public class AAGroupCreateViewController: AAViewController, UITextFieldDelegate { + private let isChannel: Bool private var addPhotoButton = UIButton() private var avatarImageView = UIImageView() private var hint = UILabel() @@ -15,13 +16,18 @@ public class AAGroupCreateViewController: AAViewController, UITextFieldDelegate private var image: UIImage? - public override init(){ + public init(isChannel: Bool) { + self.isChannel = isChannel super.init(nibName: nil, bundle: nil) - self.navigationItem.title = AALocalized("CreateGroupTitle") + if isChannel { + self.navigationItem.title = AALocalized("CreateChannelTitle") + } else { + self.navigationItem.title = AALocalized("CreateGroupTitle") + } if AADevice.isiPad { self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationCancel"), style: UIBarButtonItemStyle.Plain, target: self, action: #selector(AAViewController.dismiss)) } - self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationNext"), style: UIBarButtonItemStyle.Plain, target: self, action: #selector(AAGroupCreateViewController.doNext)) + self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationNext"), style: UIBarButtonItemStyle.Done, target: self, action: #selector(AAGroupCreateViewController.doNext)) } public required init(coder aDecoder: NSCoder) { @@ -77,7 +83,11 @@ public class AAGroupCreateViewController: AAViewController, UITextFieldDelegate groupName.font = UIFont.systemFontOfSize(20) groupName.keyboardType = UIKeyboardType.Default groupName.returnKeyType = UIReturnKeyType.Next - groupName.attributedPlaceholder = NSAttributedString(string: AALocalized("CreateGroupNamePlaceholder"), attributes: [NSForegroundColorAttributeName: ActorSDK.sharedActor().style.vcHintColor]) + if isChannel { + groupName.attributedPlaceholder = NSAttributedString(string: AALocalized("CreateChannelNamePlaceholder"), attributes: [NSForegroundColorAttributeName: ActorSDK.sharedActor().style.vcHintColor]) + } else { + groupName.attributedPlaceholder = NSAttributedString(string: AALocalized("CreateGroupNamePlaceholder"), attributes: [NSForegroundColorAttributeName: ActorSDK.sharedActor().style.vcHintColor]) + } groupName.delegate = self groupName.contentVerticalAlignment = UIControlContentVerticalAlignment.Center groupName.autocapitalizationType = UITextAutocapitalizationType.Words @@ -85,7 +95,12 @@ public class AAGroupCreateViewController: AAViewController, UITextFieldDelegate groupNameFieldSeparator.backgroundColor = appStyle.vcSeparatorColor - hint.text = AALocalized("CreateGroupHint") + if isChannel { + hint.text = AALocalized("CreateChannelHint") + } else { + hint.text = AALocalized("CreateGroupHint") + } + hint.font = UIFont.systemFontOfSize(15) hint.lineBreakMode = .ByWordWrapping hint.numberOfLines = 0 @@ -130,12 +145,7 @@ public class AAGroupCreateViewController: AAViewController, UITextFieldDelegate groupName.becomeFirstResponder() } -// public override func viewDidAppear(animated: Bool) { -// super.viewDidAppear(animated) -// -// groupName.becomeFirstResponder() -// } - + public func textFieldShouldReturn(textField: UITextField) -> Bool { doNext() return false @@ -148,6 +158,12 @@ public class AAGroupCreateViewController: AAViewController, UITextFieldDelegate return } - navigateNext(GroupMembersController(title: title, image: image), removeCurrent: true) + if isChannel { + executePromise(Actor.createChannelWithTitle(title, withAvatar: nil)).then({ (gid: JavaLangInteger!) in + self.navigateNext(AAGroupTypeViewController(gid: Int(gid.intValue()), isCreation: true), removeCurrent: true) + }) + } else { + navigateNext(GroupMembersController(title: title, image: image), removeCurrent: true) + } } } \ No newline at end of file diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Compose/AAGroupMembersController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Compose/AAGroupMembersController.swift index 3c337f6b79..7eefccf47e 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Compose/AAGroupMembersController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Compose/AAGroupMembersController.swift @@ -25,7 +25,7 @@ public class GroupMembersController: AAContactsListContentController, AAContacts navigationItem.title = AALocalized("CreateGroupMembersTitle") if AADevice.isiPad { - self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationCancel"), style: UIBarButtonItemStyle.Plain, target: self, action: Selector("dismiss")) + self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationCancel"), style: UIBarButtonItemStyle.Plain, target: self, action: #selector(dismiss)) } navigationItem.rightBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationDone"), style: UIBarButtonItemStyle.Done, target: self, action: #selector(GroupMembersController.doNext)) @@ -81,18 +81,18 @@ public class GroupMembersController: AAContactsListContentController, AAContacts res.replaceIntAtIndex(UInt(i), withInt: selected[i].contact.uid) } -// executeSafeOnlySuccess(Actor.createGroupWithTitle(groupTitle, withAvatar: nil, withUids: res)) { (val) -> Void in -// let gid = (val as! JavaLangInteger).intValue -// if self.groupImage != nil { -// Actor.changeGroupAvatar(gid, image: self.groupImage!) -// } -// if let customController = ActorSDK.sharedActor().delegate.actorControllerForConversation(ACPeer.groupWithInt(gid)) { -// self.navigateDetail(customController) -// } else { -// self.navigateDetail(ConversationViewController(peer: ACPeer.groupWithInt(gid))) -// } -// self.dismiss() -// } + executePromise(Actor.createGroupWithTitle(groupTitle, withAvatar: nil, withUids: res)).then { (res: JavaLangInteger!) in + let gid = res.intValue + if self.groupImage != nil { + Actor.changeGroupAvatar(gid, image: self.groupImage!) + } + if let customController = ActorSDK.sharedActor().delegate.actorControllerForConversation(ACPeer.groupWithInt(gid)) { + self.navigateDetail(customController) + } else { + self.navigateDetail(ConversationViewController(peer: ACPeer.groupWithInt(gid))) + } + self.dismiss() + } } // Handling token input updates diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupAdministrationViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupAdministrationViewController.swift index 954fd3eadb..6722ed2e40 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupAdministrationViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupAdministrationViewController.swift @@ -8,6 +8,7 @@ public class AAGroupAdministrationViewController: AAContentTableController { private var isChannel: Bool = false private var shortNameRow: AACommonRow! + private var shareHistoryRow: AACommonRow! public init(gid: Int) { super.init(style: .SettingsGrouped) @@ -43,19 +44,26 @@ public class AAGroupAdministrationViewController: AAContentTableController { if group.isCanEditAdministration.get().booleanValue() { r.style = .Navigation r.selectAction = { () -> Bool in - self.navigateNext(AAGroupTypeViewController(gid: self.gid)) + self.navigateNext(AAGroupTypeViewController(gid: self.gid, isCreation: false)) return false } } }) } - if group.isCanEditAdministration.get().booleanValue() { + if group.isCanEditAdministration.get().booleanValue() && !isChannel { section { (s) in s.footerText = "All members will see all messages" - s.common({ (r) in + self.shareHistoryRow = s.common({ (r) in r.content = "Share History" - r.hint = "Shared" + r.bindAction = { (r) in + if self.group.isHistoryShared.get().booleanValue() { + r.hint = "Shared" + } else { + r.hint = nil + } + } + }) } } @@ -77,10 +85,17 @@ public class AAGroupAdministrationViewController: AAContentTableController { } public override func tableWillBind(binder: AABinder) { + binder.bind(self.group.shortName) { (value: String!) in if let row = self.shortNameRow { row.reload() } } + + binder.bind(self.group.isHistoryShared) { (value: JavaLangBoolean!) in + if let row = self.shareHistoryRow { + row.reload() + } + } } } \ No newline at end of file diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupTypeController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupTypeController.swift index 8e62df437a..fdb97b8a1e 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupTypeController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupTypeController.swift @@ -6,6 +6,7 @@ import Foundation public class AAGroupTypeViewController: AAContentTableController { + private let isCreation: Bool private var isChannel: Bool = false private var isPublic: Bool = false private var linkSection: AAManagedSection! @@ -13,7 +14,8 @@ public class AAGroupTypeViewController: AAContentTableController { private var privateRow: AACommonRow! private var shortNameRow: AAEditRow! - public init(gid: Int) { + public init(gid: Int, isCreation: Bool) { + self.isCreation = isCreation super.init(style: .SettingsGrouped) self.gid = gid self.isChannel = group.groupType == ACGroupType.CHANNEL() @@ -22,7 +24,11 @@ public class AAGroupTypeViewController: AAContentTableController { } else { navigationItem.title = AALocalized("GroupTypeTitle") } - navigationItem.rightBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationSave"), style: .Done, target: self, action: #selector(saveDidTap)) + if isCreation { + navigationItem.rightBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationNext"), style: .Done, target: self, action: #selector(saveDidTap)) + } else { + navigationItem.rightBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationSave"), style: .Done, target: self, action: #selector(saveDidTap)) + } } public required init(coder aDecoder: NSCoder) { @@ -115,10 +121,28 @@ public class AAGroupTypeViewController: AAContentTableController { if nShortName != group.shortName.get() { executePromise(Actor.editGroupShortNameWithGid(jint(self.gid), withAbout: nShortName).then({ (r:ARVoid!) in - self.navigateBack() + if (self.isCreation) { + if let customController = ActorSDK.sharedActor().delegate.actorControllerForConversation(ACPeer.groupWithInt(jint(self.gid))) { + self.navigateDetail(customController) + } else { + self.navigateDetail(ConversationViewController(peer: ACPeer.groupWithInt(jint(self.gid)))) + } + self.dismiss() + } else { + self.navigateBack() + } })) } else { - navigateBack() + if (isCreation) { + if let customController = ActorSDK.sharedActor().delegate.actorControllerForConversation(ACPeer.groupWithInt(jint(self.gid))) { + self.navigateDetail(customController) + } else { + self.navigateDetail(ConversationViewController(peer: ACPeer.groupWithInt(jint(self.gid)))) + } + self.dismiss() + } else { + navigateBack() + } } } } \ No newline at end of file diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewController.swift index bf39264f62..9cb838e86b 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewController.swift @@ -348,7 +348,7 @@ public class AAGroupViewController: AAContentTableController { title = AALocalized("GroupLeaveConfirm") } self.confirmDestructive(title, action: AALocalized("GroupLeaveConfirmAction"), yes: { () -> () in - // self.executeSafe(Actor.leaveGroupCommandWithGid(jint(self.gid))!) + self.executeSafe(Actor.leaveGroupCommandWithGid(jint(self.gid))) }) return true From 7d91c4f20a44663f3777d7bfa7d999363737f668 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Fri, 22 Jul 2016 13:33:06 +0300 Subject: [PATCH 098/414] feat(scheme): New permissions --- actor-sdk/sdk-api/actor.json | 20 +++++++++++++ .../models/im/actor/api/scheme.mps | 28 +++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/actor-sdk/sdk-api/actor.json b/actor-sdk/sdk-api/actor.json index ac8c351100..f65656214e 100644 --- a/actor-sdk/sdk-api/actor.json +++ b/actor-sdk/sdk-api/actor.json @@ -7936,6 +7936,22 @@ { "name": "EDIT_ADMINS", "id": 8 + }, + { + "name": "KICK_INVITED", + "id": 9 + }, + { + "name": "KICK_ANYONE", + "id": 10 + }, + { + "name": "EDIT_FOREIGN", + "id": 11 + }, + { + "name": "DELETE_FOREIGN", + "id": 12 } ] } @@ -8237,6 +8253,10 @@ "5 - canEditAdminSettings. Default is FALSE.", "6 - canViewAdmins. Default is FALSE.", "7 - canEditAdmins. Default is FALSE.", + "8 - canKickInvited. Default is FALSE.", + "9 - canKickAnyone. Default is FALSE.", + "10 - canEditForeign. Default is FALSE.", + "11 - canDeleteForeign. Default is FALSE.", "", { "type": "reference", diff --git a/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps b/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps index f25e2f6eba..0e6847fc56 100644 --- a/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps +++ b/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps @@ -6974,6 +6974,22 @@ + + + + + + + + + + + + + + + + @@ -7370,6 +7386,18 @@ + + + + + + + + + + + + From a72ac63e7836d6fe02890ab725fdda9b19a48507 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Fri, 22 Jul 2016 13:43:00 +0300 Subject: [PATCH 099/414] feat(core): Added new permissions support --- .../core/api/ApiGroupFullPermissions.java | 8 +++ .../main/java/im/actor/core/entity/Group.java | 36 +++++++++++ .../java/im/actor/core/viewmodel/GroupVM.java | 64 +++++++++++++++++++ 3 files changed, 108 insertions(+) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiGroupFullPermissions.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiGroupFullPermissions.java index 1c015ddb01..502c59432c 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiGroupFullPermissions.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiGroupFullPermissions.java @@ -15,6 +15,10 @@ public enum ApiGroupFullPermissions { EDIT_ADMIN_SETTINGS(6), VIEW_ADMINS(7), EDIT_ADMINS(8), + KICK_INVITED(9), + KICK_ANYONE(10), + EDIT_FOREIGN(11), + DELETE_FOREIGN(12), UNSUPPORTED_VALUE(-1); private int value; @@ -37,6 +41,10 @@ public static ApiGroupFullPermissions parse(int value) throws IOException { case 6: return ApiGroupFullPermissions.EDIT_ADMIN_SETTINGS; case 7: return ApiGroupFullPermissions.VIEW_ADMINS; case 8: return ApiGroupFullPermissions.EDIT_ADMINS; + case 9: return ApiGroupFullPermissions.KICK_INVITED; + case 10: return ApiGroupFullPermissions.KICK_ANYONE; + case 11: return ApiGroupFullPermissions.EDIT_FOREIGN; + case 12: return ApiGroupFullPermissions.DELETE_FOREIGN; default: return ApiGroupFullPermissions.UNSUPPORTED_VALUE; } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Group.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Group.java index 5b417d601b..f34565a673 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Group.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Group.java @@ -106,6 +106,14 @@ public class Group extends WrapperExtEntity implements K private boolean isCanViewAdmins; @Property("readonly, nonatomic") private boolean isCanEditAdmins; + @Property("readonly, nonatomic") + private boolean isCanKickInvited; + @Property("readonly, nonatomic") + private boolean isCanKickAnyone; + @Property("readonly, nonatomic") + private boolean isCanEditForeign; + @Property("readonly, nonatomic") + private boolean isCanDeleteForeign; @Property("readonly, nonatomic") private boolean haveExtension; @@ -254,6 +262,22 @@ public boolean isCanInviteViaLink() { return isCanInviteViaLink; } + public boolean isCanKickInvited() { + return isCanKickInvited; + } + + public boolean isCanKickAnyone() { + return isCanKickAnyone; + } + + public boolean isCanEditForeign() { + return isCanEditForeign; + } + + public boolean isCanDeleteForeign() { + return isCanDeleteForeign; + } + public Group updateExt(@Nullable ApiGroupFull ext) { return new Group(getWrapped(), ext); } @@ -759,6 +783,10 @@ protected void applyWrapped(@NotNull ApiGroup wrapped, @Nullable ApiGroupFull ex # 5 - canEditAdminSettings. Default is FALSE. # 6 - canViewAdmins. Default is FALSE. # 7 - canEditAdmins. Default is FALSE. + # 8 - canKickInvited. Default is FALSE. + # 9 - canKickAnyone. Default is FALSE. + # 10 - canEditForeign. Default is FALSE. + # 11 - canDeleteForeign. Default is FALSE. */ long fullPermissions = 0; if (ext.getPermissions() != null) { @@ -772,6 +800,10 @@ protected void applyWrapped(@NotNull ApiGroup wrapped, @Nullable ApiGroupFull ex this.isCanEditAdministration = BitMaskUtil.getBitValue(fullPermissions, ApiGroupFullPermissions.EDIT_ADMIN_SETTINGS); this.isCanViewAdmins = BitMaskUtil.getBitValue(fullPermissions, ApiGroupFullPermissions.VIEW_ADMINS); this.isCanEditAdmins = BitMaskUtil.getBitValue(fullPermissions, ApiGroupFullPermissions.EDIT_ADMINS); + this.isCanKickInvited = BitMaskUtil.getBitValue(fullPermissions, ApiGroupFullPermissions.KICK_INVITED); + this.isCanKickAnyone = BitMaskUtil.getBitValue(fullPermissions, ApiGroupFullPermissions.KICK_ANYONE); + this.isCanEditForeign = BitMaskUtil.getBitValue(fullPermissions, ApiGroupFullPermissions.EDIT_FOREIGN); + this.isCanDeleteForeign = BitMaskUtil.getBitValue(fullPermissions, ApiGroupFullPermissions.DELETE_FOREIGN); this.members = new ArrayList<>(); for (ApiMember m : ext.getMembers()) { @@ -794,6 +826,10 @@ protected void applyWrapped(@NotNull ApiGroup wrapped, @Nullable ApiGroupFull ex this.isCanViewAdmins = false; this.isCanEditAdmins = false; this.isCanInviteViaLink = false; + this.isCanKickInvited = false; + this.isCanKickAnyone = false; + this.isCanEditForeign = false; + this.isCanDeleteForeign = false; } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java index 11e9ba0fe6..a6d5454534 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java @@ -93,6 +93,18 @@ public class GroupVM extends BaseValueModel { @NotNull @Property("nonatomic, readonly") private BooleanValueModel isCanInviteViaLink; + @NotNull + @Property("nonatomic, readonly") + private BooleanValueModel isCanKickInvited; + @NotNull + @Property("nonatomic, readonly") + private BooleanValueModel isCanKickAnyone; + @NotNull + @Property("nonatomic, readonly") + private BooleanValueModel isCanEditForeign; + @NotNull + @Property("nonatomic, readonly") + private BooleanValueModel isCanDeleteForeign; @NotNull @Property("nonatomic, readonly") @@ -139,6 +151,10 @@ public GroupVM(@NotNull Group rawObj) { this.isCanLeave = new BooleanValueModel("group." + groupId + ".isCanLeave", rawObj.isCanLeave()); this.isCanDelete = new BooleanValueModel("group." + groupId + ".isCanDelete", rawObj.isCanDelete()); this.isCanInviteViaLink = new BooleanValueModel("group." + groupId + ".isCanInviteViaLink", rawObj.isCanInviteViaLink()); + this.isCanKickInvited = new BooleanValueModel("group." + groupId + ".isCanKickInvited", rawObj.isCanKickInvited()); + this.isCanKickAnyone = new BooleanValueModel("group." + groupId + ".isCanKickAnyone", rawObj.isCanKickAnyone()); + this.isCanEditForeign = new BooleanValueModel("group." + groupId + ".isCanEditForeign", rawObj.isCanEditForeign()); + this.isCanDeleteForeign = new BooleanValueModel("group." + groupId + ".isCanDeleteForeign", rawObj.isCanDeleteForeign()); this.ownerId = new IntValueModel("group." + groupId + ".membersCount", rawObj.getOwnerId()); this.members = new ValueModel<>("group." + groupId + ".members", new HashSet<>(rawObj.getMembers())); @@ -368,6 +384,50 @@ public BooleanValueModel getIsCanInviteViaLink() { return isCanInviteViaLink; } + /** + * Is current user can kick invited members + * + * @return is current user can kick invited model + */ + @NotNull + @ObjectiveCName("getIsCanKickInvitedModel") + public BooleanValueModel getIsCanKickInvited() { + return isCanKickInvited; + } + + /** + * Is current user can kick anyone + * + * @return is current user can kick anyone model + */ + @NotNull + @ObjectiveCName("getIsCanKickAnyoneModel") + public BooleanValueModel getIsCanKickAnyone() { + return isCanKickAnyone; + } + + /** + * Is current user can edit foreign messages + * + * @return is current user can edit foreign messages model + */ + @NotNull + @ObjectiveCName("getIsCanEditForeignModel") + public BooleanValueModel getIsCanEditForeign() { + return isCanEditForeign; + } + + /** + * Is current user can delete foreign messages + * + * @return is current user can delete foreign messages model + */ + @NotNull + @ObjectiveCName("getIsCanDeleteForeignModel") + public BooleanValueModel getIsCanDeleteForeign() { + return isCanDeleteForeign; + } + /** * Get Group owner user id model * @@ -473,6 +533,10 @@ protected void updateValues(@NotNull Group rawObj) { isChanged |= isCanLeave.change(rawObj.isCanLeave()); isChanged |= isCanDelete.change(rawObj.isCanDelete()); isChanged |= isCanInviteViaLink.change(rawObj.isCanInviteViaLink()); + isChanged |= isCanKickInvited.change(rawObj.isCanKickInvited()); + isChanged |= isCanKickAnyone.change(rawObj.isCanKickAnyone()); + isChanged |= isCanEditForeign.change(rawObj.isCanEditForeign()); + isChanged |= isCanDeleteForeign.change(rawObj.isCanDeleteForeign()); if (isChanged) { notifyIfNeeded(); From 07204946f97812bd8287b8fdaeb5ed71380d8c89 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Fri, 22 Jul 2016 13:49:32 +0300 Subject: [PATCH 100/414] feat(android): Respect can kick permissions --- .../controllers/group/AddMemberActivity.java | 1 - .../controllers/group/AddMemberFragment.java | 1 - .../controllers/group/GroupEditFragment.java | 1 - .../controllers/group/GroupInfoFragment.java | 28 +++++++++++++------ .../controllers/group/GroupTypeFragment.java | 1 - .../controllers/group/MembersFragment.java | 6 ---- 6 files changed, 20 insertions(+), 18 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/AddMemberActivity.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/AddMemberActivity.java index 57bb60eb4f..2418ac7f1a 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/AddMemberActivity.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/AddMemberActivity.java @@ -2,7 +2,6 @@ import android.os.Bundle; -import im.actor.sdk.R; import im.actor.sdk.controllers.Intents; import im.actor.sdk.controllers.activity.BaseFragmentActivity; diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/AddMemberFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/AddMemberFragment.java index 6ce85f7b25..5ca6c706bc 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/AddMemberFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/AddMemberFragment.java @@ -16,7 +16,6 @@ import im.actor.sdk.controllers.Intents; import im.actor.sdk.controllers.contacts.BaseContactFragment; -import static im.actor.sdk.util.ActorSDKMessenger.groups; import static im.actor.sdk.util.ActorSDKMessenger.messenger; import static im.actor.sdk.util.ActorSDKMessenger.users; diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupEditFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupEditFragment.java index fb1665c998..47a9049bb7 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupEditFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupEditFragment.java @@ -1,6 +1,5 @@ package im.actor.sdk.controllers.group; -import android.net.Uri; import android.os.Bundle; import android.support.v7.app.AlertDialog; import android.text.Editable; diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java index a323e15cec..4a1b336467 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java @@ -302,7 +302,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa if (groupMember.getUid() != myUid()) { UserVM userVM = users().get(groupMember.getUid()); if (userVM != null) { - onMemberClicked(userVM); + onMemberClicked(userVM, groupMember.getInviterUid() == myUid()); return true; } } @@ -352,14 +352,26 @@ public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCoun return res; } - public void onMemberClicked(UserVM userVM) { + public void onMemberClicked(UserVM userVM, boolean isInvitedByMe) { + + CharSequence[] items; + if (groupVM.getIsCanKickAnyone().get() || (groupVM.getIsCanKickInvited().get() && isInvitedByMe)) { + items = new CharSequence[]{ + getString(R.string.group_context_message).replace("{0}", userVM.getName().get()), + getString(R.string.group_context_call).replace("{0}", userVM.getName().get()), + getString(R.string.group_context_view).replace("{0}", userVM.getName().get()), + getString(R.string.group_context_remove).replace("{0}", userVM.getName().get()), + }; + } else { + items = new CharSequence[]{ + getString(R.string.group_context_message).replace("{0}", userVM.getName().get()), + getString(R.string.group_context_call).replace("{0}", userVM.getName().get()), + getString(R.string.group_context_view).replace("{0}", userVM.getName().get()) + }; + } + new AlertDialog.Builder(getActivity()) - .setItems(new CharSequence[]{ - getString(R.string.group_context_message).replace("{0}", userVM.getName().get()), - getString(R.string.group_context_call).replace("{0}", userVM.getName().get()), - getString(R.string.group_context_view).replace("{0}", userVM.getName().get()), - getString(R.string.group_context_remove).replace("{0}", userVM.getName().get()), - }, (dialog, which) -> { + .setItems(items, (dialog, which) -> { if (which == 0) { startActivity(Intents.openPrivateDialog(userVM.getId(), true, getActivity())); } else if (which == 1) { diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupTypeFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupTypeFragment.java index 76f269c6df..f9f7b0f01e 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupTypeFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupTypeFragment.java @@ -17,7 +17,6 @@ import im.actor.sdk.ActorSDK; import im.actor.sdk.R; import im.actor.sdk.controllers.BaseFragment; -import im.actor.sdk.util.Fonts; import static im.actor.sdk.util.ActorSDKMessenger.messenger; diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/MembersFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/MembersFragment.java index 499dc422ed..2dc940d76e 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/MembersFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/MembersFragment.java @@ -8,13 +8,9 @@ import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; -import android.widget.ListView; import android.widget.TextView; -import java.util.ArrayList; - import fr.castorflex.android.circularprogressbar.CircularProgressBar; -import im.actor.core.entity.GroupMember; import im.actor.core.viewmodel.GroupVM; import im.actor.sdk.ActorSDK; import im.actor.sdk.R; @@ -23,11 +19,9 @@ import im.actor.sdk.controllers.group.view.MembersAdapter; import im.actor.sdk.util.Screen; import im.actor.sdk.view.DividerView; -import im.actor.sdk.view.adapters.HeaderViewRecyclerAdapter; import im.actor.sdk.view.adapters.RecyclerListView; import static im.actor.sdk.util.ActorSDKMessenger.groups; -import static im.actor.sdk.util.ActorSDKMessenger.messenger; public class MembersFragment extends BaseFragment { From 261e927838e0a7d99712ab02e238dffabd1e30e3 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Fri, 22 Jul 2016 15:46:28 +0300 Subject: [PATCH 101/414] feat(iOS): Localized group type and administration controllers, respect new permissions --- .../controllers/group/MembersFragment.java | 16 ++--- .../Resources/Base.lproj/Localizable.strings | 59 +++++++++++++++++ .../Resources/es.lproj/Localizable.strings | 64 +++++++++++++++++++ .../Resources/pt.lproj/Localizable.strings | 59 +++++++++++++++++ .../Resources/ru.lproj/Localizable.strings | 61 ++++++++++++++++++ .../zh-Hans.lproj/Localizable.strings | 55 ++++++++++++++++ .../ActorSDK/Sources/ActorSDK.swift | 3 + .../AAAddParticipantViewController.swift | 18 +++--- .../AAGroupAdministrationViewController.swift | 53 +++++++++++---- .../Group/AAGroupTypeController.swift | 60 +++++++++++++---- .../Group/AAGroupViewController.swift | 35 ++-------- .../Controllers/Managed Runtime/Alerts.swift | 11 ++++ .../main/java/im/actor/core/entity/Group.java | 7 ++ .../core/modules/groups/GroupsProcessor.java | 2 + .../modules/groups/router/GroupRouter.java | 9 +++ .../java/im/actor/core/viewmodel/GroupVM.java | 16 +++++ 16 files changed, 454 insertions(+), 74 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/MembersFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/MembersFragment.java index 2dc940d76e..f2be8361c2 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/MembersFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/MembersFragment.java @@ -69,12 +69,9 @@ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, addMmemberTV.setGravity(Gravity.CENTER_VERTICAL); addMmemberTV.setText(R.string.group_add_member); addMmemberTV.setTextColor(ActorSDK.sharedActor().style.getTextPrimaryColor()); - addMmemberTV.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - startActivity(new Intent(getActivity(), AddMemberActivity.class) - .putExtra(Intents.EXTRA_GROUP_ID, groupId)); - } + addMmemberTV.setOnClickListener(view -> { + startActivity(new Intent(getActivity(), AddMemberActivity.class) + .putExtra(Intents.EXTRA_GROUP_ID, groupId)); }); header.addView(addMmemberTV, ViewGroup.LayoutParams.MATCH_PARENT, Screen.dp(58)); @@ -92,12 +89,7 @@ public void onClick(View view) { shareLinkTV.setGravity(Gravity.CENTER_VERTICAL); shareLinkTV.setTextColor(ActorSDK.sharedActor().style.getTextPrimaryColor()); shareLinkTV.setText(R.string.invite_link_action_share); - shareLinkTV.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - Intents.inviteLink(groupId, getActivity()); - } - }); + shareLinkTV.setOnClickListener(view -> Intents.inviteLink(groupId, getActivity())); header.addView(shareLinkTV, ViewGroup.LayoutParams.MATCH_PARENT, Screen.dp(58)); } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/Base.lproj/Localizable.strings b/actor-sdk/sdk-core-ios/ActorSDK/Resources/Base.lproj/Localizable.strings index 848b3e9449..cfccc2d3e3 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Resources/Base.lproj/Localizable.strings +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/Base.lproj/Localizable.strings @@ -300,6 +300,65 @@ "GroupTypeTitleChannel" = "Channel Type"; +"GroupPermissionsHint" = "Control what is possible in this group"; + +"GroupPermissionsHintChannel" = "Control what is possible in this channel"; + +"GroupTypeHintPublic" = "Public groups can be found in search and anyone can join"; + +"GroupTypeHintPrivate" = "Private groups can be joined only via personal invitation"; + +"GroupTypeHintPublicChannel" = "Public channels can be found in search and anyone can join"; + +"GroupTypeHintPrivateChannel" = "Private channels can be joined only via personal invitation"; + +"GroupTypeLinkHint" = "People can share this link with others and find your group using search"; + +"GroupTypeLinkHintChannel" = "People can share this link with others and find your channel using search"; + +"GroupTypePublic" = "Public"; + +"GroupTypePrivate" = "Private"; + +"ChannelTypePublic" = "Public"; + +"ChannelTypePrivate" = "Private"; + +"GroupTypePublicFull" = "Public Group"; + +"GroupTypePrivateFull" = "Private Group"; + +"ChannelTypePublicFull" = "Public Channel"; + +"ChannelTypePrivateFull" = "Private Channel"; + + +"GroupShareTitle" = "Shared History"; + +"GroupShareEnabled" = "Shared"; + +"GroupShareHint" = "All members will see all messages"; + +"GroupShareMessage" = "Are you sure want to share all messages to all members? This action is irreversible."; + +"GroupShareAction" = "Share"; + + +"GroupDeleteTitle" = "Delete Group"; + +"GroupDeleteTitleChannel" = "Delete Channel"; + +"GroupDeleteHint" = "You will lose all messages in this group"; + +"GroupDeleteHintChannel" = "You will lose all messages in this channel"; + +"GroupDeleteMessage" = "Are you sure want to DELETE group and ALL messages in it? This action irreversible."; + +"GroupDeleteMessageChannel" = "Are you sure want to DELETE channel and ALL messages in it? This action irreversible."; + +"GroupDeleteAction" = "Delete ALL"; + + "GroupMemberAdmin" = "admin"; "GroupMemberInfo" = "Profile"; diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/es.lproj/Localizable.strings b/actor-sdk/sdk-core-ios/ActorSDK/Resources/es.lproj/Localizable.strings index f255c963bd..b925b5c5e8 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Resources/es.lproj/Localizable.strings +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/es.lproj/Localizable.strings @@ -293,10 +293,74 @@ "GroupAdministration" = "Administration"; + + "GroupTypeTitle" = "Group Type"; "GroupTypeTitleChannel" = "Channel Type"; +"GroupTypePublic" = "Public"; + +"GroupTypePrivate" = "Private"; + +"ChannelTypePublic" = "Public"; + +"ChannelTypePrivate" = "Private"; + +"GroupTypePublicFull" = "Public Group"; + +"GroupTypePrivateFull" = "Private Group"; + +"ChannelTypePublicFull" = "Public Channel"; + +"ChannelTypePrivateFull" = "Private Channel"; + +"GroupPermissionsHint" = "Control what is possible in this group"; + +"GroupPermissionsHintChannel" = "Control what is possible in this channel"; + +"GroupTypeHintPublic" = "Public groups can be found in search and anyone can join"; + +"GroupTypeHintPrivate" = "Private groups can be joined only via personal invitation"; + +"GroupTypeHintPublicChannel" = "Public channels can be found in search and anyone can join"; + +"GroupTypeHintPrivateChannel" = "Private channels can be joined only via personal invitation"; + +"GroupTypeLinkHint" = "People can share this link with others and find your group using search"; + +"GroupTypeLinkHintChannel" = "People can share this link with others and find your channel using search"; + + + +"GroupShareTitle" = "Shared History"; + +"GroupShareEnabled" = "Shared"; + +"GroupShareHint" = "All members will see all messages"; + +"GroupShareMessage" = "Are you sure want to share all messages to all members? This action is irreversible."; + +"GroupShareAction" = "Share"; + + + +"GroupDeleteTitle" = "Delete Group"; + +"GroupDeleteTitleChannel" = "Delete Channel"; + +"GroupDeleteHint" = "You will lose all messages in this group"; + +"GroupDeleteHintChannel" = "You will lose all messages in this channel"; + +"GroupDeleteMessage" = "Are you sure want to DELETE group and ALL messages in it? This action irreversible."; + +"GroupDeleteMessageChannel" = "Are you sure want to DELETE channel and ALL messages in it? This action irreversible."; + +"GroupDeleteAction" = "Delete ALL"; + + + "GroupMemberAdmin" = "admin"; "GroupMemberInfo" = "Perfil"; diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/pt.lproj/Localizable.strings b/actor-sdk/sdk-core-ios/ActorSDK/Resources/pt.lproj/Localizable.strings index 297a9dbe26..6c455d95e9 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Resources/pt.lproj/Localizable.strings +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/pt.lproj/Localizable.strings @@ -286,6 +286,65 @@ "GroupTypeTitleChannel" = "Channel Type"; +"GroupTypePublic" = "Public"; + +"GroupTypePrivate" = "Private"; + +"ChannelTypePublic" = "Public"; + +"ChannelTypePrivate" = "Private"; + +"GroupTypePublicFull" = "Public Group"; + +"GroupTypePrivateFull" = "Private Group"; + +"ChannelTypePublicFull" = "Public Channel"; + +"ChannelTypePrivateFull" = "Private Channel"; + +"GroupPermissionsHint" = "Control what is possible in this group"; + +"GroupPermissionsHintChannel" = "Control what is possible in this channel"; + +"GroupTypeHintPublic" = "Public groups can be found in search and anyone can join"; + +"GroupTypeHintPrivate" = "Private groups can be joined only via personal invitation"; + +"GroupTypeHintPublicChannel" = "Public channels can be found in search and anyone can join"; + +"GroupTypeHintPrivateChannel" = "Private channels can be joined only via personal invitation"; + +"GroupTypeLinkHint" = "People can share this link with others and find your group using search"; + +"GroupTypeLinkHintChannel" = "People can share this link with others and find your channel using search"; + + +"GroupShareTitle" = "Shared History"; + +"GroupShareEnabled" = "Shared"; + +"GroupShareHint" = "All members will see all messages"; + +"GroupShareMessage" = "Are you sure want to share all messages to all members? This action is irreversible."; + +"GroupShareAction" = "Share"; + + +"GroupDeleteTitle" = "Delete Group"; + +"GroupDeleteTitleChannel" = "Delete Channel"; + +"GroupDeleteHint" = "You will lose all messages in this group"; + +"GroupDeleteHintChannel" = "You will lose all messages in this channel"; + +"GroupDeleteMessage" = "Are you sure want to DELETE group and ALL messages in it? This action irreversible."; + +"GroupDeleteMessageChannel" = "Are you sure want to DELETE channel and ALL messages in it? This action irreversible."; + +"GroupDeleteAction" = "Delete ALL"; + + "GroupViewMembers" = "Membros"; "GroupMemberAdmin" = "admin"; diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/ru.lproj/Localizable.strings b/actor-sdk/sdk-core-ios/ActorSDK/Resources/ru.lproj/Localizable.strings index 1036d7bfa1..38492e2ffd 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Resources/ru.lproj/Localizable.strings +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/ru.lproj/Localizable.strings @@ -280,6 +280,7 @@ "GroupEditConfirm" = "Вы уверены, что хотите изменить тему группы?"; + "GroupEditConfirmAction" = "Изменить тему"; "GroupNotifications" = "Оповещения"; @@ -298,6 +299,66 @@ "GroupTypeTitleChannel" = "Тип канала"; +"GroupTypePublic" = "Публичная"; + +"GroupTypePrivate" = "Приватная"; + +"ChannelTypePublic" = "Публичный"; + +"ChannelTypePrivate" = "Приватный"; + +"GroupTypePublicFull" = "Публичная группа"; + +"GroupTypePrivateFull" = "Приватная группа"; + +"ChannelTypePublicFull" = "Публичный канал"; + +"ChannelTypePrivateFull" = "Приватный канал"; + +"GroupPermissionsHint" = "Управление тем что возможно делать в этой группе"; + +"GroupPermissionsHintChannel" = "Управление тем что возможно делать в этом канале"; + +"GroupTypeHintPublic" = "Публичные гурппы могут быть найдены в поиске и кто угодно может войти в нее"; + +"GroupTypeHintPrivate" = "Вступить в приватную группу можно только по личному приглашению"; + +"GroupTypeHintPublicChannel" = "Публичные каналы могут быть найдены в поиске и кто угодно может подписаться на него"; + +"GroupTypeHintPrivateChannel" = "Вступить в приватный канал можно только по личному приглашению"; + +"GroupTypeLinkHint" = "Ваши друзья могут делиться этой ссылкой или находить группу в поиске"; + +"GroupTypeLinkHintChannel" = "Ваши друзья могут делиться этой ссылкой или находить канал в поиске"; + + +"GroupShareTitle" = "Общая история"; + +"GroupShareEnabled" = "Включено"; + +"GroupShareHint" = "Все участники увидят все сообщения группы"; + +"GroupShareMessage" = "Вы уверены что хотите сделать все сообщениям общими? Это действие необратимо."; + +"GroupShareAction" = "Сделать Общими"; + + +"GroupDeleteTitle" = "Удалить группу"; + +"GroupDeleteTitleChannel" = "Удалить канал"; + +"GroupDeleteHint" = "Вы потеряете все сообщения в этой группе"; + +"GroupDeleteHintChannel" = "Вы потеряете все сообщения в этом канале"; + +"GroupDeleteMessage" = "Вы уверены что хотите УДАЛИТЬ группу и ВСЕ сообщения в ней? Это действие необратимо."; + +"GroupDeleteMessageChannel" = "Вы уверены что хотите УДАЛИТЬ канал и ВСЕ сообщения в нем? Это действие необратимо."; + +"GroupDeleteAction" = "Удалить ВСЕ"; + + + "GroupViewMembers" = "Участники"; "GroupMemberAdmin" = "админ."; diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/zh-Hans.lproj/Localizable.strings b/actor-sdk/sdk-core-ios/ActorSDK/Resources/zh-Hans.lproj/Localizable.strings index 258aa99fa4..0390f81652 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Resources/zh-Hans.lproj/Localizable.strings +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/zh-Hans.lproj/Localizable.strings @@ -286,6 +286,61 @@ "GroupTypeTitleChannel" = "Channel Type"; +"ChannelTypePublic" = "Public"; + +"ChannelTypePrivate" = "Private"; + +"GroupTypePublicFull" = "Public Group"; + +"GroupTypePrivateFull" = "Private Group"; + +"ChannelTypePublicFull" = "Public Channel"; + +"ChannelTypePrivateFull" = "Private Channel"; + +"GroupPermissionsHint" = "Control what is possible in this group"; + +"GroupPermissionsHintChannel" = "Control what is possible in this channel"; + +"GroupTypeHintPublic" = "Public groups can be found in search and anyone can join"; + +"GroupTypeHintPrivate" = "Private groups can be joined only via personal invitation"; + +"GroupTypeHintPublicChannel" = "Public channels can be found in search and anyone can join"; + +"GroupTypeHintPrivateChannel" = "Private channels can be joined only via personal invitation"; + +"GroupTypeLinkHint" = "People can share this link with others and find your group using search"; + +"GroupTypeLinkHintChannel" = "People can share this link with others and find your channel using search"; + + +"GroupShareTitle" = "Shared History"; + +"GroupShareEnabled" = "Shared"; + +"GroupShareHint" = "All members will see all messages"; + +"GroupShareMessage" = "Are you sure want to share all messages to all members? This action is irreversible."; + +"GroupShareAction" = "Share"; + + +"GroupDeleteTitle" = "Delete Group"; + +"GroupDeleteTitleChannel" = "Delete Channel"; + +"GroupDeleteHint" = "You will lose all messages in this group"; + +"GroupDeleteHintChannel" = "You will lose all messages in this channel"; + +"GroupDeleteMessage" = "Are you sure want to DELETE group and ALL messages in it? This action irreversible."; + +"GroupDeleteMessageChannel" = "Are you sure want to DELETE channel and ALL messages in it? This action irreversible."; + +"GroupDeleteAction" = "Delete ALL"; + + "GroupMemberMakeAdmin" = "设为群组管理员"; "GroupMemberMakeMessage" = "确定将 {name} 设为群组管理员吗?"; diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorSDK.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorSDK.swift index 427782ab61..8c2f8ebcb7 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorSDK.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorSDK.swift @@ -83,6 +83,9 @@ import DZNWebViewController /// Invitation URL for apps public var invitePrefix: String? = "https://actor.im/join/" + + /// Invitation URL for apps + public var invitePrefixShort: String? = "actor.im/join/" /// Privacy Policy URL public var privacyPolicyUrl: String? = nil diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAAddParticipantViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAAddParticipantViewController.swift index 154f045414..ffb5491e76 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAAddParticipantViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAAddParticipantViewController.swift @@ -29,14 +29,16 @@ public class AAAddParticipantViewController: AAContactsListContentController, AA } public func willAddContacts(controller: AAContactsListContentController, section: AAManagedSection) { - section.custom { (r:AACustomRow) -> () in - r.height = 56 - r.closure = { (cell) -> () in - cell.bind("ic_invite_user", actionTitle: AALocalized("GroupAddParticipantUrl")) - } - r.selectAction = { () -> Bool in - self.navigateNext(AAInviteLinkViewController(gid: self.gid), removeCurrent: false) - return false + if group.isCanInviteViaLink.get().booleanValue() { + section.custom { (r:AACustomRow) -> () in + r.height = 56 + r.closure = { (cell) -> () in + cell.bind("ic_invite_user", actionTitle: AALocalized("GroupAddParticipantUrl")) + } + r.selectAction = { () -> Bool in + self.navigateNext(AAInviteLinkViewController(gid: self.gid), removeCurrent: false) + return false + } } } } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupAdministrationViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupAdministrationViewController.swift index 6722ed2e40..70e789f1a7 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupAdministrationViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupAdministrationViewController.swift @@ -24,7 +24,11 @@ public class AAGroupAdministrationViewController: AAContentTableController { public override func tableDidLoad() { section { (s) in - s.footerText = "Control what is possible in this group" + if isChannel { + s.footerText = AALocalized("GroupPermissionsHintChannel") + } else { + s.footerText = AALocalized("GroupPermissionsHint") + } self.shortNameRow = s.common({ (r) in if (self.isChannel) { @@ -35,9 +39,17 @@ public class AAGroupAdministrationViewController: AAContentTableController { r.bindAction = { (r) in if self.group.shortName.get() != nil { - r.hint = "Public" + if self.isChannel { + r.hint = AALocalized("ChannelTypePublic") + } else { + r.hint = AALocalized("GroupTypePublic") + } } else { - r.hint = "Private" + if self.isChannel { + r.hint = AALocalized("ChannelTypePrivate") + } else { + r.hint = AALocalized("GroupTypePrivate") + } } } @@ -53,30 +65,45 @@ public class AAGroupAdministrationViewController: AAContentTableController { if group.isCanEditAdministration.get().booleanValue() && !isChannel { section { (s) in - s.footerText = "All members will see all messages" + s.footerText = AALocalized("GroupShareHint") self.shareHistoryRow = s.common({ (r) in - r.content = "Share History" + r.content = AALocalized("GroupShareTitle") r.bindAction = { (r) in if self.group.isHistoryShared.get().booleanValue() { - r.hint = "Shared" + r.hint = AALocalized("GroupShareEnabled") + r.selectAction = nil } else { r.hint = nil + r.selectAction = { () -> Bool in + self.confirmAlertUser("GroupShareMessage", action: "GroupShareAction", tapYes: { + self.executePromise(Actor.shareHistoryWithGid(jint(self.gid))) + }) + return true + } } } - }) } } if group.isCanDelete.get().booleanValue() { section { (s) in - s.footerText = "You will lose all messages in this group" - s.danger("Delete Group", closure: { (r) in + let action: String + if isChannel { + s.footerText = AALocalized("GroupDeleteHintChannel") + action = AALocalized("GroupDeleteTitleChannel") + } else { + s.footerText = AALocalized("GroupDeleteHint") + action = AALocalized("GroupDeleteTitle") + } + s.danger(action, closure: { (r) in r.selectAction = { () -> Bool in - self.executePromise(Actor.deleteGroupWithGid(jint(self.gid))).after { - let first = self.navigationController!.viewControllers.first! - self.navigationController!.setViewControllers([first], animated: true) - } + self.confirmAlertUserDanger(self.isChannel ? "GroupDeleteMessageChannel" : "GroupDeleteMessage", action: "GroupDeleteAction", tapYes: { + self.executePromise(Actor.deleteGroupWithGid(jint(self.gid))).after { + let first = self.navigationController!.viewControllers.first! + self.navigationController!.setViewControllers([first], animated: true) + } + }) return true } }) diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupTypeController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupTypeController.swift index fdb97b8a1e..88b13d0665 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupTypeController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupTypeController.swift @@ -41,20 +41,38 @@ public class AAGroupTypeViewController: AAContentTableController { section { (s) in - s.headerText = "Group Type".uppercaseString - if self.isPublic { - s.footerText = "Public groups can be found in search and anyone can joing" + if isChannel { + s.headerText = AALocalized("GroupTypeTitleChannel").uppercaseString + if self.isPublic { + s.footerText = AALocalized("GroupTypeHintPublicChannel") + } else { + s.footerText = AALocalized("GroupTypeHintPrivateChannel") + } } else { - s.footerText = "Private groups can be joined only via personal invitation" + s.headerText = AALocalized("GroupTypeTitle").uppercaseString + if self.isPublic { + s.footerText = AALocalized("GroupTypeHintPublic") + } else { + s.footerText = AALocalized("GroupTypeHintPrivate") + } } self.publicRow = s.common({ (r) in - r.content = "Public Group" + if isChannel { + r.content = AALocalized("ChannelTypePublicFull") + } else { + r.content = AALocalized("GroupTypePublicFull") + } + r.selectAction = { () -> Bool in if !self.isPublic { self.isPublic = true self.publicRow.rebind() self.privateRow.rebind() - s.footerText = "Public groups can be found in search and anyone can joing" + if self.isChannel { + s.footerText = AALocalized("GroupTypeHintPublicChannel") + } else { + s.footerText = AALocalized("GroupTypeHintPublic") + } self.tableView.reloadSection(0, withRowAnimation: .Automatic) self.managedTable.sections.append(self.linkSection) self.tableView.insertSection(1, withRowAnimation: .Fade) @@ -71,13 +89,22 @@ public class AAGroupTypeViewController: AAContentTableController { }) self.privateRow = s.common({ (r) in - r.content = "Private Group" + if isChannel { + r.content = AALocalized("ChannelTypePrivateFull") + } else { + r.content = AALocalized("GroupTypePrivateFull") + } + r.selectAction = { () -> Bool in if self.isPublic { self.isPublic = false self.publicRow.rebind() self.privateRow.rebind() - s.footerText = "Private groups can be joined only via personal invitation" + if self.isChannel { + s.footerText = AALocalized("GroupTypeHintPrivateChannel") + } else { + s.footerText = AALocalized("GroupTypeHintPrivate") + } self.tableView.reloadSection(0, withRowAnimation: .Automatic) self.managedTable.sections.removeAtIndex(1) self.tableView.deleteSection(1, withRowAnimation: .Fade) @@ -95,10 +122,15 @@ public class AAGroupTypeViewController: AAContentTableController { } self.linkSection = section { (s) in - s.footerText = "People can share this link with others and find your channel using search." + if self.isChannel { + s.footerText = AALocalized("GroupTypeLinkHintChannel") + } else { + s.footerText = AALocalized("GroupTypeLinkHint") + } + self.shortNameRow = s.edit({ (r) in r.autocapitalizationType = .None - r.prefix = "actor.im/join/" + r.prefix = ActorSDK.sharedActor().invitePrefixShort r.text = self.group.shortName.get() }) } @@ -110,8 +142,12 @@ public class AAGroupTypeViewController: AAContentTableController { public func saveDidTap() { let nShortName: String? if self.isPublic { - if self.shortNameRow.text!.trim().length > 0 { - nShortName = self.shortNameRow.text!.trim() + if let shortNameVal = self.shortNameRow.text?.trim() { + if shortNameVal.length > 0 { + nShortName = shortNameVal + } else { + nShortName = nil + } } else { nShortName = nil } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewController.swift index 9cb838e86b..4ccc5bc76f 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewController.swift @@ -281,41 +281,18 @@ public class AAGroupViewController: AAContentTableController { }) } }) - - // Detect if we are admin - let members: [ACGroupMember] = self.group.members.get().toArray().toSwiftArray() - var isAdmin = self.group.ownerId.get()?.intValue() == Actor.myUid() - if !isAdmin { - for m in members { - if m.uid == Actor.myUid() { - isAdmin = m.isAdministrator - } - } - } - - // - // // Can mark as admin - // let canMarkAdmin = isAdmin && !d.isAdministrator - // - // if canMarkAdmin { - // a.action("GroupMemberMakeAdmin") { () -> () in - // - // self.confirmDestructive(AALocalized("GroupMemberMakeMessage").replace("{name}", dest: name), action: AALocalized("GroupMemberMakeAction")) { - // - // self.executeSafe(Actor.makeAdminCommandWithGid(jint(self.gid), withUid: jint(user.getId()))!) - // } - // } - // } // Can kick user - let canKick = isAdmin || d.inviterUid == Actor.myUid() - let name = Actor.getUserWithUid(d.uid).getNameModel().get() + let canKick: Bool = + (self.group.isCanKickAnyone.get().booleanValue() || + (self.group.isCanKickInvited.get().booleanValue() && d.inviterUid == Actor.myUid())) + if canKick { + let name = Actor.getUserWithUid(d.uid).getNameModel().get() a.destructive("GroupMemberKick") { () -> () in self.confirmDestructive(AALocalized("GroupMemberKickMessage") .replace("{name}", dest: name), action: AALocalized("GroupMemberKickAction")) { - -// self.executeSafe(Actor.kickMemberCommandWithGid(jint(self.gid), withUid: user.getId())!) + self.executeSafe(Actor.kickMemberCommandWithGid(jint(self.gid), withUid: user.getId())) } } } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/Alerts.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/Alerts.swift index 215e68eaef..b2c444b2f5 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/Alerts.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/Alerts.swift @@ -26,6 +26,17 @@ public extension UIViewController { self.presentViewController(controller, animated: true, completion: nil) } + public func confirmAlertUserDanger(message: String, action: String, tapYes: ()->(), tapNo: (()->())? = nil) { + let controller = UIAlertController(title: nil, message: AALocalized(message), preferredStyle: UIAlertControllerStyle.Alert) + controller.addAction(UIAlertAction(title: AALocalized(action), style: UIAlertActionStyle.Destructive, handler: { (alertView) -> () in + tapYes() + })) + controller.addAction(UIAlertAction(title: AALocalized("AlertCancel"), style: UIAlertActionStyle.Cancel, handler: { (alertView) -> () in + tapNo?() + })) + self.presentViewController(controller, animated: true, completion: nil) + } + public func confirmDangerSheetUser(action: String, tapYes: ()->(), tapNo: (()->())?) { showActionSheet(nil, buttons: [], cancelButton: "AlertCancel", destructButton: action, sourceView: UIView(), sourceRect: CGRectZero) { (index) -> () in if index == -2 { diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Group.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Group.java index f34565a673..39603d0061 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Group.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Group.java @@ -62,6 +62,8 @@ public class Group extends WrapperExtEntity implements K private boolean isCanLeave; @Property("readonly, nonatomic") private boolean isCanDelete; + @Property("readonly, nonatomic") + private boolean isDeleted; @NotNull @Property("readonly, nonatomic") @SuppressWarnings("NullableProblems") @@ -168,6 +170,10 @@ public boolean isMember() { return isMember; } + public boolean isDeleted() { + return isDeleted; + } + public boolean isCanSendMessage() { return isCanSendMessage; } @@ -728,6 +734,7 @@ protected void applyWrapped(@NotNull ApiGroup wrapped, @Nullable ApiGroupFull ex this.isHidden = wrapped.isHidden() != null ? wrapped.isHidden() : false; this.membersCount = wrapped.getMembersCount() != null ? wrapped.getMembersCount() : 0; this.isMember = wrapped.isMember() != null ? wrapped.isMember() : true; + this.isDeleted = wrapped.isDeleted() != null ? wrapped.isDeleted() : false; if (wrapped.getGroupType() == null) { this.groupType = GroupType.GROUP; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsProcessor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsProcessor.java index 08f844cd52..66872e6c15 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsProcessor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsProcessor.java @@ -6,6 +6,7 @@ import im.actor.core.api.updates.UpdateGroupAboutChanged; import im.actor.core.api.updates.UpdateGroupAvatarChanged; +import im.actor.core.api.updates.UpdateGroupDeleted; import im.actor.core.api.updates.UpdateGroupExtChanged; import im.actor.core.api.updates.UpdateGroupFullExtChanged; import im.actor.core.api.updates.UpdateGroupFullPermissionsChanged; @@ -40,6 +41,7 @@ public Promise process(Update update) { update instanceof UpdateGroupMemberChanged || update instanceof UpdateGroupAvatarChanged || update instanceof UpdateGroupPermissionsChanged || + update instanceof UpdateGroupDeleted || update instanceof UpdateGroupExtChanged || update instanceof UpdateGroupMembersUpdated || diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/router/GroupRouter.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/router/GroupRouter.java index 9861166624..e2291fbea0 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/router/GroupRouter.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/router/GroupRouter.java @@ -14,6 +14,7 @@ import im.actor.core.api.rpc.RequestLoadFullGroups; import im.actor.core.api.updates.UpdateGroupAboutChanged; import im.actor.core.api.updates.UpdateGroupAvatarChanged; +import im.actor.core.api.updates.UpdateGroupDeleted; import im.actor.core.api.updates.UpdateGroupExtChanged; import im.actor.core.api.updates.UpdateGroupFullExtChanged; import im.actor.core.api.updates.UpdateGroupFullPermissionsChanged; @@ -81,6 +82,11 @@ public Promise onPermissionsChanged(int groupId, long permissions) { return editGroup(groupId, group -> group.editPermissions(permissions)); } + @Verified + public Promise onGroupDeleted(int groupId) { + return editGroup(groupId, group -> group.editIsDeleted(true)); + } + @Verified public Promise onExtChanged(int groupId, ApiMapValue ext) { return editGroup(groupId, group -> group.editExt(ext)); @@ -298,6 +304,9 @@ private Promise onUpdate(Update update) { } else if (update instanceof UpdateGroupPermissionsChanged) { UpdateGroupPermissionsChanged permissionsChanged = (UpdateGroupPermissionsChanged) update; return onPermissionsChanged(permissionsChanged.getGroupId(), permissionsChanged.getPermissions()); + } else if (update instanceof UpdateGroupDeleted) { + UpdateGroupDeleted groupDeleted = (UpdateGroupDeleted) update; + return onGroupDeleted(groupDeleted.getGroupId()); } else if (update instanceof UpdateGroupExtChanged) { UpdateGroupExtChanged extChanged = (UpdateGroupExtChanged) update; return onExtChanged(extChanged.getGroupId(), extChanged.getExt()); diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java index a6d5454534..fe5e5abd46 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java @@ -105,6 +105,9 @@ public class GroupVM extends BaseValueModel { @NotNull @Property("nonatomic, readonly") private BooleanValueModel isCanDeleteForeign; + @NotNull + @Property("nonatomic, readonly") + private BooleanValueModel isDeleted; @NotNull @Property("nonatomic, readonly") @@ -155,6 +158,7 @@ public GroupVM(@NotNull Group rawObj) { this.isCanKickAnyone = new BooleanValueModel("group." + groupId + ".isCanKickAnyone", rawObj.isCanKickAnyone()); this.isCanEditForeign = new BooleanValueModel("group." + groupId + ".isCanEditForeign", rawObj.isCanEditForeign()); this.isCanDeleteForeign = new BooleanValueModel("group." + groupId + ".isCanDeleteForeign", rawObj.isCanDeleteForeign()); + this.isDeleted = new BooleanValueModel("group." + groupId + ".isDeleted", rawObj.isDeleted()); this.ownerId = new IntValueModel("group." + groupId + ".membersCount", rawObj.getOwnerId()); this.members = new ValueModel<>("group." + groupId + ".members", new HashSet<>(rawObj.getMembers())); @@ -428,6 +432,17 @@ public BooleanValueModel getIsCanDeleteForeign() { return isCanDeleteForeign; } + /** + * Is group deleted + * + * @return is this group deleted model + */ + @NotNull + @ObjectiveCName("getIsDeletedModel") + public BooleanValueModel getIsDeleted() { + return isDeleted; + } + /** * Get Group owner user id model * @@ -537,6 +552,7 @@ protected void updateValues(@NotNull Group rawObj) { isChanged |= isCanKickAnyone.change(rawObj.isCanKickAnyone()); isChanged |= isCanEditForeign.change(rawObj.isCanEditForeign()); isChanged |= isCanDeleteForeign.change(rawObj.isCanDeleteForeign()); + isChanged |= isDeleted.change(rawObj.isDeleted()); if (isChanged) { notifyIfNeeded(); From eb1d7859c6cb509084b15b5d452ed5ec7fdadee7 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Fri, 22 Jul 2016 16:06:44 +0300 Subject: [PATCH 102/414] feat(scheme): Added LeaveAndDelete, added members to loadMembers response --- actor-sdk/sdk-api/actor.json | 45 ++++++++++++++++++- .../models/im/actor/api/scheme.mps | 37 ++++++++++++++- 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/actor-sdk/sdk-api/actor.json b/actor-sdk/sdk-api/actor.json index f65656214e..ced200d431 100644 --- a/actor-sdk/sdk-api/actor.json +++ b/actor-sdk/sdk-api/actor.json @@ -8486,7 +8486,7 @@ "doc": [ { "type": "reference", - "argument": "members", + "argument": "users", "category": "full", "description": " Group members" }, @@ -8498,6 +8498,17 @@ } ], "attributes": [ + { + "type": { + "type": "list", + "childType": { + "type": "struct", + "childType": "Member" + } + }, + "id": 3, + "name": "members" + }, { "type": { "type": "list", @@ -8507,7 +8518,7 @@ } }, "id": 1, - "name": "members" + "name": "users" }, { "type": { @@ -10068,6 +10079,36 @@ ] } }, + { + "type": "rpc", + "content": { + "name": "LeaveAndDelete", + "header": 2721, + "response": { + "type": "reference", + "name": "Seq" + }, + "doc": [ + "Leave group and Delete Chat", + { + "type": "reference", + "argument": "groupPeer", + "category": "full", + "description": "Group peer" + } + ], + "attributes": [ + { + "type": { + "type": "struct", + "childType": "GroupOutPeer" + }, + "id": 1, + "name": "groupPeer" + } + ] + } + }, { "type": "rpc", "content": { diff --git a/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps b/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps index 0e6847fc56..c6c6aa2405 100644 --- a/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps +++ b/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps @@ -7519,9 +7519,18 @@ + + + + + + + + + - + @@ -7541,7 +7550,7 @@ - + @@ -8823,6 +8832,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + From cedae207dfe061e1d4f493bfbfa7dbce7adb58d9 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Fri, 22 Jul 2016 16:37:50 +0300 Subject: [PATCH 103/414] feat(iOS): Localized group/channel edit controller --- .../Resources/Base.lproj/Localizable.strings | 11 ++++++ .../Resources/es.lproj/Localizable.strings | 12 +++++++ .../Resources/pt.lproj/Localizable.strings | 15 ++++++++ .../Resources/ru.lproj/Localizable.strings | 12 +++++++ .../zh-Hans.lproj/Localizable.strings | 13 +++++++ .../Group/AAGroupEditInfoViewController.swift | 34 +++++++++++++------ .../Group/AAGroupViewController.swift | 2 +- .../Sources/Views/Cells/AATextCell.swift | 6 ++-- 8 files changed, 90 insertions(+), 15 deletions(-) diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/Base.lproj/Localizable.strings b/actor-sdk/sdk-core-ios/ActorSDK/Resources/Base.lproj/Localizable.strings index cfccc2d3e3..d167990d8d 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Resources/Base.lproj/Localizable.strings +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/Base.lproj/Localizable.strings @@ -359,6 +359,17 @@ "GroupDeleteAction" = "Delete ALL"; +"GroupEditTitle" = "Edit Group"; + +"GroupEditTitleChannel" = "Edit Channel"; + +"GroupEditName" = "Group Name"; + +"GroupEditNameChannel" = "Channel Name"; + +"GroupEditDescription" = "Description"; + + "GroupMemberAdmin" = "admin"; "GroupMemberInfo" = "Profile"; diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/es.lproj/Localizable.strings b/actor-sdk/sdk-core-ios/ActorSDK/Resources/es.lproj/Localizable.strings index b925b5c5e8..02ce86c036 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Resources/es.lproj/Localizable.strings +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/es.lproj/Localizable.strings @@ -361,6 +361,18 @@ +"GroupEditTitle" = "Edit Group"; + +"GroupEditTitleChannel" = "Edit Channel"; + +"GroupEditName" = "Group Name"; + +"GroupEditNameChannel" = "Channel Name"; + +"GroupEditDescription" = "Description"; + + + "GroupMemberAdmin" = "admin"; "GroupMemberInfo" = "Perfil"; diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/pt.lproj/Localizable.strings b/actor-sdk/sdk-core-ios/ActorSDK/Resources/pt.lproj/Localizable.strings index 6c455d95e9..6bddaeda1f 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Resources/pt.lproj/Localizable.strings +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/pt.lproj/Localizable.strings @@ -319,6 +319,7 @@ "GroupTypeLinkHintChannel" = "People can share this link with others and find your channel using search"; + "GroupShareTitle" = "Shared History"; "GroupShareEnabled" = "Shared"; @@ -330,6 +331,7 @@ "GroupShareAction" = "Share"; + "GroupDeleteTitle" = "Delete Group"; "GroupDeleteTitleChannel" = "Delete Channel"; @@ -345,6 +347,19 @@ "GroupDeleteAction" = "Delete ALL"; + +"GroupEditTitle" = "Edit Group"; + +"GroupEditTitleChannel" = "Edit Channel"; + +"GroupEditName" = "Group Name"; + +"GroupEditNameChannel" = "Channel Name"; + +"GroupEditDescription" = "Description"; + + + "GroupViewMembers" = "Membros"; "GroupMemberAdmin" = "admin"; diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/ru.lproj/Localizable.strings b/actor-sdk/sdk-core-ios/ActorSDK/Resources/ru.lproj/Localizable.strings index 38492e2ffd..86cf4598a0 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Resources/ru.lproj/Localizable.strings +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/ru.lproj/Localizable.strings @@ -359,6 +359,18 @@ +"GroupEditTitle" = "Редактирование"; + +"GroupEditTitleChannel" = "Редактирование"; + +"GroupEditName" = "Название группы"; + +"GroupEditNameChannel" = "Название канала"; + +"GroupEditDescription" = "Описание"; + + + "GroupViewMembers" = "Участники"; "GroupMemberAdmin" = "админ."; diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/zh-Hans.lproj/Localizable.strings b/actor-sdk/sdk-core-ios/ActorSDK/Resources/zh-Hans.lproj/Localizable.strings index 0390f81652..c1cdf1b0ff 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Resources/zh-Hans.lproj/Localizable.strings +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/zh-Hans.lproj/Localizable.strings @@ -341,6 +341,19 @@ "GroupDeleteAction" = "Delete ALL"; + +"GroupEditTitle" = "Edit Group"; + +"GroupEditTitleChannel" = "Edit Channel"; + +"GroupEditName" = "Group Name"; + +"GroupEditNameChannel" = "Channel Name"; + +"GroupEditDescription" = "Description"; + + + "GroupMemberMakeAdmin" = "设为群组管理员"; "GroupMemberMakeMessage" = "确定将 {name} 设为群组管理员吗?"; diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupEditInfoViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupEditInfoViewController.swift index 5a0354447e..20d360353d 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupEditInfoViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupEditInfoViewController.swift @@ -7,6 +7,7 @@ import SZTextView public class AAGroupEditInfoController: AAViewController, UITextViewDelegate { + private var isChannel = false private let scrollView = UIScrollView() private let bgContainer = UIView() private let topSeparator = UIView() @@ -21,6 +22,7 @@ public class AAGroupEditInfoController: AAViewController, UITextViewDelegate { public init(gid: Int) { super.init() self.gid = gid + self.isChannel = group.groupType == ACGroupType.CHANNEL() } public required init(coder aDecoder: NSCoder) { @@ -56,18 +58,27 @@ public class AAGroupEditInfoController: AAViewController, UITextViewDelegate { self.avatarDidTap() } - nameInput.font = UIFont.systemFontOfSize(16) - nameInput.placeholder = "Name" + nameInput.font = UIFont.systemFontOfSize(19) + if isChannel { + nameInput.placeholder = AALocalized("GroupEditNameChannel") + } else { + nameInput.placeholder = AALocalized("GroupEditName") + } nameInput.text = group.name.get() descriptionView.delegate = self - descriptionView.font = UIFont.systemFontOfSize(16) - descriptionView.placeholder = "Description" + descriptionView.font = UIFont.systemFontOfSize(17) + descriptionView.placeholder = AALocalized("GroupEditDescription") descriptionView.text = group.about.get() descriptionView.scrollEnabled = false - navigationItem.title = "Edit Group" + if isChannel { + navigationItem.title = AALocalized("GroupEditTitleChannel") + } else { + navigationItem.title = AALocalized("GroupEditTitle") + } navigationItem.rightBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationSave"), style: .Done, target: self, action: #selector(saveDidPressed)) + navigationItem.leftBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationCancel"), style: .Done, target: self, action: #selector(dismiss)) } public override func viewWillLayoutSubviews() { @@ -113,24 +124,25 @@ public class AAGroupEditInfoController: AAViewController, UITextViewDelegate { public func saveDidPressed() { let text = nameInput.text!.trim() let about = self.descriptionView.text!.trim() - + nameInput.resignFirstResponder() + descriptionView.resignFirstResponder() if text != group.name.get() { executePromise(Actor.editGroupTitleWithGid(jint(gid), withTitle: text).then({ (v: ARVoid!) in if about != self.group.about.get() { self.executePromise(Actor.editGroupAboutWithGid(jint(self.gid), withAbout: about).then({ (v: ARVoid!) in - self.navigateBack() + self.dismiss() })) } else { - self.navigateBack() + self.dismiss() } })) } else { if about != self.group.about.get() { self.executePromise(Actor.editGroupAboutWithGid(jint(self.gid), withAbout: about).then({ (v: ARVoid!) in - self.navigateBack() + self.dismiss() })) } else { - self.navigateBack() + self.dismiss() } } } @@ -141,7 +153,7 @@ public class AAGroupEditInfoController: AAViewController, UITextViewDelegate { private func layoutContainer() { let newSize = descriptionView.sizeThatFits(CGSize(width: view.width - 20, height: CGFloat.max)) - descriptionView.frame = CGRectMake(10, 100, view.width - 20, max(newSize.height, 33)) + descriptionView.frame = CGRectMake(10, 102, view.width - 20, max(newSize.height, 33)) bgContainer.frame = CGRectMake(0, 0, view.width, 100 + descriptionView.height + 8) bottomSeparator.frame = CGRectMake(0, bgContainer.height - 0.5, bgContainer.width, 0.5) } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewController.swift index 4ccc5bc76f..82e113101c 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewController.swift @@ -377,6 +377,6 @@ public class AAGroupViewController: AAContentTableController { } public func editDidPressed() { - self.navigateNext(AAGroupEditInfoController(gid: gid)) + self.presentInNavigation(AAGroupEditInfoController(gid: gid)) } } \ No newline at end of file diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/Cells/AATextCell.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/Cells/AATextCell.swift index 7986ac6b13..2f01e2246e 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/Cells/AATextCell.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/Cells/AATextCell.swift @@ -47,12 +47,12 @@ public class AATextCell: AATableViewCell { super.layoutSubviews() if titleLabel.hidden { - contentLabel.frame = CGRect(x: 15, y: 7, width: contentView.bounds.width - 30, height: 10000) + contentLabel.frame = CGRect(x: 15, y: 8, width: contentView.bounds.width - 30, height: contentView.height - 16) } else { titleLabel.frame = CGRect(x: 15, y: 7, width: contentView.bounds.width - 30, height: titleLabel.bounds.height) contentLabel.frame = CGRect(x: 15, y: 27, width: contentView.bounds.width - 30, height: 10000) + contentLabel.sizeToFit() } - contentLabel.sizeToFit() } public class func measure(title: String?, text: String, width: CGFloat, enableNavigation: Bool) -> CGFloat { @@ -61,7 +61,7 @@ public class AATextCell: AATableViewCell { if title != nil { return CGFloat(size.height + 36) } else { - return CGFloat(size.height + 15) + return CGFloat(max(size.height + 16, 44)) } } } \ No newline at end of file From e1f8bfd25e588ef289ca9cc2c87c93042a483493 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Fri, 22 Jul 2016 16:44:56 +0300 Subject: [PATCH 104/414] fix(iOS): Fixing keyboard disappear --- .../Group/AAGroupEditInfoViewController.swift | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupEditInfoViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupEditInfoViewController.swift index 20d360353d..d10b916f0d 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupEditInfoViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupEditInfoViewController.swift @@ -78,7 +78,7 @@ public class AAGroupEditInfoController: AAViewController, UITextViewDelegate { navigationItem.title = AALocalized("GroupEditTitle") } navigationItem.rightBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationSave"), style: .Done, target: self, action: #selector(saveDidPressed)) - navigationItem.leftBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationCancel"), style: .Done, target: self, action: #selector(dismiss)) + navigationItem.leftBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationCancel"), style: .Done, target: self, action: #selector(cancelEdit)) } public override func viewWillLayoutSubviews() { @@ -130,23 +130,29 @@ public class AAGroupEditInfoController: AAViewController, UITextViewDelegate { executePromise(Actor.editGroupTitleWithGid(jint(gid), withTitle: text).then({ (v: ARVoid!) in if about != self.group.about.get() { self.executePromise(Actor.editGroupAboutWithGid(jint(self.gid), withAbout: about).then({ (v: ARVoid!) in - self.dismiss() + self.cancelEdit() })) } else { - self.dismiss() + self.cancelEdit() } })) } else { if about != self.group.about.get() { self.executePromise(Actor.editGroupAboutWithGid(jint(self.gid), withAbout: about).then({ (v: ARVoid!) in - self.dismiss() + self.cancelEdit() })) } else { - self.dismiss() + self.cancelEdit() } } } + func cancelEdit() { + nameInput.resignFirstResponder() + descriptionView.resignFirstResponder() + dismiss() + } + public func textViewDidChange(textView: UITextView) { layoutContainer() } From f8e1eda590c7f6c1fef9566311e2516469134cc1 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Fri, 22 Jul 2016 16:57:54 +0300 Subject: [PATCH 105/414] fix(iOS): Fixing not updating common cell on hide/show label --- .../sdk-core-ios/ActorSDK/Sources/Views/Cells/AACommonCell.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/Cells/AACommonCell.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/Cells/AACommonCell.swift index 74bfe9bd95..10141e598d 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/Cells/AACommonCell.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/Cells/AACommonCell.swift @@ -54,6 +54,7 @@ public class AACommonCell: AATableViewCell { hintLabel.text = hint hintLabel.hidden = false } + setNeedsLayout() } // Setting switcher content From 3dde8d1f247b633247b160b3de5326c12c19a1e3 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Fri, 22 Jul 2016 17:21:01 +0300 Subject: [PATCH 106/414] ref(iOS): Minimal code cleanup --- .../Sources/Controllers/Managed Runtime/AAManagedTable.swift | 1 - .../Sources/Controllers/Recent/AARecentViewController.swift | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/AAManagedTable.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/AAManagedTable.swift index eaed3dc426..27f532ea78 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/AAManagedTable.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/AAManagedTable.swift @@ -516,7 +516,6 @@ private class AAManagedSearchController Date: Fri, 22 Jul 2016 18:35:22 +0300 Subject: [PATCH 107/414] feat(iOS+core): Global Search --- .../AADialogsListContentController.swift | 2 +- ...DialogsListContentControllerDelegate.swift | 2 +- .../Cells/AADialogSearchCell.swift | 4 +- .../Managed Runtime/AAManagedTable.swift | 55 +++++++++--- .../Recent/AARecentViewController.swift | 2 +- .../main/java/im/actor/core/Messenger.java | 12 +++ .../im/actor/core/entity/SearchResult.java | 50 +++++++++++ .../core/modules/search/SearchModule.java | 6 ++ .../search/sources/GlobalSearchSource.java | 78 ++++++++++++++++ .../java/im/actor/runtime/mvvm/AsyncVM.java | 9 +- .../actor/runtime/mvvm/SearchValueModel.java | 88 +++++++++++++++++++ .../actor/runtime/mvvm/SearchValueSource.java | 9 ++ 12 files changed, 295 insertions(+), 22 deletions(-) create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/SearchResult.java create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/search/sources/GlobalSearchSource.java create mode 100644 actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/mvvm/SearchValueModel.java create mode 100644 actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/mvvm/SearchValueSource.java diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Dialogs List/AADialogsListContentController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Dialogs List/AADialogsListContentController.swift index 543a4d0b46..5703108077 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Dialogs List/AADialogsListContentController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Dialogs List/AADialogsListContentController.swift @@ -32,7 +32,7 @@ public class AADialogsListContentController: AAContentTableController, UISearchB if enableSearch { search(AADialogSearchCell.self) { (s) -> () in - s.searchList = Actor.buildSearchDisplayList() + s.searchModel = Actor.buildGlobalSearchModel() s.selectAction = { (itm) -> () in self.delegate?.searchDidTap(self, entity: itm) diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Dialogs List/AADialogsListContentControllerDelegate.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Dialogs List/AADialogsListContentControllerDelegate.swift index dc425faf37..26f9434ea8 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Dialogs List/AADialogsListContentControllerDelegate.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Dialogs List/AADialogsListContentControllerDelegate.swift @@ -8,5 +8,5 @@ public protocol AADialogsListContentControllerDelegate { func recentsDidTap(controller: AADialogsListContentController, dialog: ACDialog) -> Bool - func searchDidTap(controller: AADialogsListContentController, entity: ACSearchEntity) + func searchDidTap(controller: AADialogsListContentController, entity: ACSearchResult) } \ No newline at end of file diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Dialogs List/Cells/AADialogSearchCell.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Dialogs List/Cells/AADialogSearchCell.swift index 37918f2df9..ac1b90b18b 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Dialogs List/Cells/AADialogSearchCell.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Dialogs List/Cells/AADialogSearchCell.swift @@ -6,7 +6,7 @@ import UIKit public class AADialogSearchCell: AATableViewCell, AABindedSearchCell { - public typealias BindData = ACSearchEntity + public typealias BindData = ACSearchResult public static func bindedCellHeight(item: BindData) -> CGFloat { @@ -31,7 +31,7 @@ public class AADialogSearchCell: AATableViewCell, AABindedSearchCell { fatalError("init(coder:) has not been implemented") } - public func bind(item: ACSearchEntity, search: String?) { + public func bind(item: ACSearchResult, search: String?) { avatarView.bind(item.title, id: Int(item.peer.peerId), avatar: item.avatar) titleView.text = item.title } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/AAManagedTable.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/AAManagedTable.swift index 27f532ea78..5668969e39 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/AAManagedTable.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/AAManagedTable.swift @@ -405,23 +405,26 @@ private class AMBaseTableDelegate: NSObject, UITableViewDelegate, UITableViewDat public class AAManagedSearchConfig { - public var searchList: ARBindedDisplayList! + public var searchList: ARBindedDisplayList? + public var searchModel: ARSearchValueModel? public var selectAction: ((BindCell.BindData) -> ())? public var isSearchAutoHide: Bool = true public var didBind: ((c: BindCell, d: BindCell.BindData) -> ())? } -private class AAManagedSearchController: NSObject, UISearchBarDelegate, UISearchDisplayDelegate, UITableViewDataSource, UITableViewDelegate, ARDisplayList_Listener { +private class AAManagedSearchController: NSObject, UISearchBarDelegate, UISearchDisplayDelegate, UITableViewDataSource, UITableViewDelegate, ARDisplayList_Listener, ARValueChangedListener { let config: AAManagedSearchConfig - let displayList: ARBindedDisplayList + let searchList: ARBindedDisplayList? + let searchModel: ARSearchValueModel? let searchDisplay: UISearchDisplayController init(config: AAManagedSearchConfig, controller: UIViewController, tableView: UITableView) { self.config = config - self.displayList = config.searchList + self.searchList = config.searchList + self.searchModel = config.searchModel let style = ActorSDK.sharedActor().style let searchBar = UISearchBar() @@ -481,24 +484,48 @@ private class AAManagedSearchController BindCell.BindData { - return displayList.itemWithIndex(jint(indexPath.row)) as! BindCell.BindData + if let ds = searchList { + return ds.itemWithIndex(jint(indexPath.row)) as! BindCell.BindData + } else if let sm = searchModel { + let list = sm.getResults().get() as! JavaUtilList + return list.getWithInt(jint(indexPath.row)) as! BindCell.BindData + } else { + fatalError("No search model or search list is set!") + } } @objc func onCollectionChanged() { searchDisplay.searchResultsTableView.reloadData() } + @objc func onChanged(val: AnyObject!, withModel valueModel: ARValue!) { + searchDisplay.searchResultsTableView.reloadData() + } + // Table view data @objc func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return Int(displayList.size()); + if let ds = searchList { + return Int(ds.size()) + } else if let sm = searchModel { + let list = sm.getResults().get() as! JavaUtilList + return Int(list.size()) + } else { + fatalError("No search model or search list is set!") + } } @objc func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { @@ -521,11 +548,17 @@ private class AAManagedSearchController 0) { - displayList.initSearchWithQuery(normalized, withRefresh: false) + if let ds = searchList { + let normalized = searchText.trim().lowercaseString + if (normalized.length > 0) { + ds.initSearchWithQuery(normalized, withRefresh: false) + } else { + ds.initEmpty() + } + } else if let sm = searchModel { + sm.queryChangedWithNSString(searchText) } else { - displayList.initEmpty() + fatalError("No search model or search list is set!") } } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Recent/AARecentViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Recent/AARecentViewController.swift index 4a69d9b3d8..9f9df5a34a 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Recent/AARecentViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Recent/AARecentViewController.swift @@ -124,7 +124,7 @@ public class AARecentViewController: AADialogsListContentController, AADialogsLi return false } - public func searchDidTap(controller: AADialogsListContentController, entity: ACSearchEntity) { + public func searchDidTap(controller: AADialogsListContentController, entity: ACSearchResult) { if let customController = ActorSDK.sharedActor().delegate.actorControllerForConversation(entity.peer) { self.navigateDetail(customController) } else { diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java index a07d9c5977..14ee5f3cec 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java @@ -28,6 +28,7 @@ import im.actor.core.entity.Peer; import im.actor.core.entity.PeerSearchEntity; import im.actor.core.entity.PeerSearchType; +import im.actor.core.entity.SearchResult; import im.actor.core.entity.Sex; import im.actor.core.entity.User; import im.actor.core.entity.WebActionDescriptor; @@ -70,6 +71,7 @@ import im.actor.runtime.actors.ActorSystem; import im.actor.runtime.actors.messages.Void; import im.actor.runtime.mvvm.MVVMCollection; +import im.actor.runtime.mvvm.SearchValueModel; import im.actor.runtime.mvvm.ValueModel; import im.actor.runtime.promise.Promise; import im.actor.runtime.storage.PreferencesStorage; @@ -1196,6 +1198,16 @@ public Command> findAllPhotos(Peer peer) { .failure(e -> callback.onError(e)); } + /** + * Building global search model + * + * @return search model + */ + @ObjectiveCName("buildGlobalSearchModel") + public SearchValueModel buildGlobalSearchModel() { + return modules.getSearchModule().buildSearchModel(); + } + ////////////////////////////////////// // Calls ////////////////////////////////////// diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/SearchResult.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/SearchResult.java new file mode 100644 index 0000000000..4b7e25b3b8 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/SearchResult.java @@ -0,0 +1,50 @@ +package im.actor.core.entity; + +import com.google.j2objc.annotations.Property; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class SearchResult { + + @NotNull + @Property("readonly, nonatomic") + private final Peer peer; + @Nullable + @Property("readonly, nonatomic") + private final Avatar avatar; + @NotNull + @Property("readonly, nonatomic") + private final String title; + @Nullable + @Property("readonly, nonatomic") + private final String matchString; + + public SearchResult(@NotNull Peer peer, @Nullable Avatar avatar, @NotNull String title, + @Nullable String matchString) { + this.peer = peer; + this.avatar = avatar; + this.title = title; + this.matchString = matchString; + } + + @NotNull + public Peer getPeer() { + return peer; + } + + @Nullable + public Avatar getAvatar() { + return avatar; + } + + @NotNull + public String getTitle() { + return title; + } + + @Nullable + public String getMatchString() { + return matchString; + } +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/search/SearchModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/search/SearchModule.java index 5dff5fe657..5450bb2269 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/search/SearchModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/search/SearchModule.java @@ -23,13 +23,16 @@ import im.actor.core.entity.PeerSearchEntity; import im.actor.core.entity.PeerSearchType; import im.actor.core.entity.SearchEntity; +import im.actor.core.entity.SearchResult; import im.actor.core.entity.content.AbsContent; import im.actor.core.modules.AbsModule; import im.actor.core.modules.api.ApiSupportConfiguration; import im.actor.core.modules.Modules; +import im.actor.core.modules.search.sources.GlobalSearchSource; import im.actor.runtime.Storage; import im.actor.runtime.actors.ActorRef; import im.actor.runtime.collections.ManagedList; +import im.actor.runtime.mvvm.SearchValueModel; import im.actor.runtime.promise.Promise; import im.actor.runtime.storage.ListEngine; @@ -134,6 +137,9 @@ public Promise> findPeers(ArrayList c .map(r -> new PeerSearchEntity(convert(r.getPeer()), r.getOptMatchString()))); } + public SearchValueModel buildSearchModel() { + return new SearchValueModel<>(new GlobalSearchSource(context())); + } // // Local Search diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/search/sources/GlobalSearchSource.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/search/sources/GlobalSearchSource.java new file mode 100644 index 0000000000..888893ed90 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/search/sources/GlobalSearchSource.java @@ -0,0 +1,78 @@ +package im.actor.core.modules.search.sources; + +import java.util.ArrayList; +import java.util.List; + +import im.actor.core.entity.Group; +import im.actor.core.entity.PeerSearchEntity; +import im.actor.core.entity.PeerType; +import im.actor.core.entity.SearchEntity; +import im.actor.core.entity.SearchResult; +import im.actor.core.modules.AbsModule; +import im.actor.core.modules.ModuleContext; +import im.actor.core.viewmodel.UserVM; +import im.actor.runtime.function.Consumer; +import im.actor.runtime.mvvm.SearchValueSource; +import im.actor.runtime.storage.ListEngine; +import im.actor.runtime.storage.ListEngineDisplayExt; + +public class GlobalSearchSource extends AbsModule implements SearchValueSource { + + public GlobalSearchSource(ModuleContext context) { + super(context); + } + + @Override + public void loadResults(String query, Consumer> callback) { + ListEngine searchList = context().getSearchModule().getSearchList(); + if (searchList instanceof ListEngineDisplayExt) { + ((ListEngineDisplayExt) searchList).loadBackward(query, 20, (items, topSortKey, bottomSortKey) -> { + ArrayList localResults = new ArrayList<>(); + for (SearchEntity e : items) { + localResults.add(new SearchResult(e.getPeer(), e.getAvatar(), e.getTitle(), + null)); + } + callback.apply(new ArrayList<>(localResults)); + if (query.length() > 3) { + loadGlobalResults(query, localResults, callback); + } + }); + } else { + if (query.length() > 3) { + loadGlobalResults(query, new ArrayList<>(), callback); + } else { + callback.apply(new ArrayList<>()); + } + } + } + + + private void loadGlobalResults(String query, ArrayList localResults, Consumer> callback) { + context().getSearchModule().findPeers(query).then(r -> { + ArrayList results = new ArrayList<>(); + outer: + for (PeerSearchEntity peerSearch : r) { + for (SearchResult l : localResults) { + if (peerSearch.getPeer().equals(l.getPeer())) { + continue outer; + } + } + if (peerSearch.getPeer().getPeerType() == PeerType.GROUP) { + Group group = context().getGroupsModule().getGroups().getValue(peerSearch.getPeer().getPeerId()); + results.add(new SearchResult(peerSearch.getPeer(), group.getAvatar(), group.getTitle(), + peerSearch.getOptMatchString())); + } else if (peerSearch.getPeer().getPeerType() == PeerType.PRIVATE) { + UserVM user = context().getUsersModule().getUsers().get(peerSearch.getPeer().getPeerId()); + results.add(new SearchResult(peerSearch.getPeer(), user.getAvatar().get(), user.getName().get(), + peerSearch.getOptMatchString())); + } + } + if (results.size() > 0) { + ArrayList combined = new ArrayList<>(); + combined.addAll(localResults); + combined.addAll(results); + callback.apply(combined); + } + }); + } +} diff --git a/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/mvvm/AsyncVM.java b/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/mvvm/AsyncVM.java index 384f58565d..8c6eb0122e 100644 --- a/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/mvvm/AsyncVM.java +++ b/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/mvvm/AsyncVM.java @@ -9,12 +9,9 @@ public abstract class AsyncVM { private boolean isDetached; protected final void post(final Object obj) { - im.actor.runtime.Runtime.postToMainThread(new Runnable() { - @Override - public void run() { - if (!isDetached) { - onObjectReceived(obj); - } + im.actor.runtime.Runtime.postToMainThread(() -> { + if (!isDetached) { + onObjectReceived(obj); } }); } diff --git a/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/mvvm/SearchValueModel.java b/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/mvvm/SearchValueModel.java new file mode 100644 index 0000000000..ae74a86a35 --- /dev/null +++ b/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/mvvm/SearchValueModel.java @@ -0,0 +1,88 @@ +package im.actor.runtime.mvvm; + +import java.util.ArrayList; +import java.util.List; + +import im.actor.runtime.annotations.MainThread; + +public class SearchValueModel extends AsyncVM { + + private SearchValueSource searchValueSource; + private int requestId = 0; + private ValueModel> results; + + public SearchValueModel(SearchValueSource searchValueSource) { + this.searchValueSource = searchValueSource; + this.results = new ValueModel<>("search.results", new ArrayList<>()); + } + + public ValueModel> getResults() { + return results; + } + + @MainThread + public void queryChanged(String query) { + + final int currentRequestId = ++requestId; + + // Filtering out trivial sources + if (query == null) { + postResults(new ArrayList<>(), currentRequestId); + return; + } + query = query.trim(); + if (query.length() == 0) { + postResults(new ArrayList<>(), currentRequestId); + return; + } + + // Non-trivial + searchValueSource.loadResults(query, r -> { + if (currentRequestId == requestId) { + postResults(r, currentRequestId); + } + }); + } + + @MainThread + protected void onResultsReceived(List res) { + results.changeInUIThread(res); + } + + // + // Internal Loop + // + + private void postResults(List res, int requestIndex) { + post(new Results<>(res, requestIndex)); + } + + @Override + protected void onObjectReceived(Object obj) { + if (obj instanceof Results) { + Results r = (Results) obj; + if (r.getRequestIndex() == requestId) { + onResultsReceived(r.getRes()); + } + } + } + + protected static class Results { + + private List res; + private int requestIndex; + + public Results(List res, int requestIndex) { + this.res = res; + this.requestIndex = requestIndex; + } + + public List getRes() { + return res; + } + + public int getRequestIndex() { + return requestIndex; + } + } +} \ No newline at end of file diff --git a/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/mvvm/SearchValueSource.java b/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/mvvm/SearchValueSource.java new file mode 100644 index 0000000000..c20a78b864 --- /dev/null +++ b/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/mvvm/SearchValueSource.java @@ -0,0 +1,9 @@ +package im.actor.runtime.mvvm; + +import java.util.List; + +import im.actor.runtime.function.Consumer; + +public interface SearchValueSource { + void loadResults(String query, Consumer> callback); +} From bd45374ccdcb41ab7e19d4b2a97d5e5a9264697c Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Fri, 22 Jul 2016 19:06:23 +0300 Subject: [PATCH 108/414] feat(core): Updated API --- .../im/actor/core/api/parser/RpcParser.java | 1 + .../core/api/rpc/RequestLeaveAndDelete.java | 65 +++++++++++++++++++ .../core/api/rpc/ResponseLoadMembers.java | 27 ++++++-- 3 files changed, 86 insertions(+), 7 deletions(-) create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/RequestLeaveAndDelete.java diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/parser/RpcParser.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/parser/RpcParser.java index 514263a785..709b69ab59 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/parser/RpcParser.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/parser/RpcParser.java @@ -87,6 +87,7 @@ public RpcScope read(int type, byte[] payload) throws IOException { case 213: return RequestEditGroupAbout.fromBytes(payload); case 69: return RequestInviteUser.fromBytes(payload); case 70: return RequestLeaveGroup.fromBytes(payload); + case 2721: return RequestLeaveAndDelete.fromBytes(payload); case 71: return RequestKickUser.fromBytes(payload); case 2784: return RequestMakeUserAdmin.fromBytes(payload); case 2791: return RequestDismissUserAdmin.fromBytes(payload); diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/RequestLeaveAndDelete.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/RequestLeaveAndDelete.java new file mode 100644 index 0000000000..810dd0526f --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/RequestLeaveAndDelete.java @@ -0,0 +1,65 @@ +package im.actor.core.api.rpc; +/* + * Generated by the Actor API Scheme generator. DO NOT EDIT! + */ + +import im.actor.runtime.bser.*; +import im.actor.runtime.collections.*; +import static im.actor.runtime.bser.Utils.*; +import im.actor.core.network.parser.*; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.NotNull; +import com.google.j2objc.annotations.ObjectiveCName; +import java.io.IOException; +import java.util.List; +import java.util.ArrayList; +import im.actor.core.api.*; + +public class RequestLeaveAndDelete extends Request { + + public static final int HEADER = 0xaa1; + public static RequestLeaveAndDelete fromBytes(byte[] data) throws IOException { + return Bser.parse(new RequestLeaveAndDelete(), data); + } + + private ApiGroupOutPeer groupPeer; + + public RequestLeaveAndDelete(@NotNull ApiGroupOutPeer groupPeer) { + this.groupPeer = groupPeer; + } + + public RequestLeaveAndDelete() { + + } + + @NotNull + public ApiGroupOutPeer getGroupPeer() { + return this.groupPeer; + } + + @Override + public void parse(BserValues values) throws IOException { + this.groupPeer = values.getObj(1, new ApiGroupOutPeer()); + } + + @Override + public void serialize(BserWriter writer) throws IOException { + if (this.groupPeer == null) { + throw new IOException(); + } + writer.writeObject(1, this.groupPeer); + } + + @Override + public String toString() { + String res = "rpc LeaveAndDelete{"; + res += "groupPeer=" + this.groupPeer; + res += "}"; + return res; + } + + @Override + public int getHeaderKey() { + return HEADER; + } +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/ResponseLoadMembers.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/ResponseLoadMembers.java index 3410fd4287..a0936c222a 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/ResponseLoadMembers.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/ResponseLoadMembers.java @@ -22,11 +22,13 @@ public static ResponseLoadMembers fromBytes(byte[] data) throws IOException { return Bser.parse(new ResponseLoadMembers(), data); } - private List members; + private List members; + private List users; private byte[] next; - public ResponseLoadMembers(@NotNull List members, @Nullable byte[] next) { + public ResponseLoadMembers(@NotNull List members, @NotNull List users, @Nullable byte[] next) { this.members = members; + this.users = users; this.next = next; } @@ -35,10 +37,15 @@ public ResponseLoadMembers() { } @NotNull - public List getMembers() { + public List getMembers() { return this.members; } + @NotNull + public List getUsers() { + return this.users; + } + @Nullable public byte[] getNext() { return this.next; @@ -46,17 +53,23 @@ public byte[] getNext() { @Override public void parse(BserValues values) throws IOException { - List _members = new ArrayList(); + List _members = new ArrayList(); + for (int i = 0; i < values.getRepeatedCount(3); i ++) { + _members.add(new ApiMember()); + } + this.members = values.getRepeatedObj(3, _members); + List _users = new ArrayList(); for (int i = 0; i < values.getRepeatedCount(1); i ++) { - _members.add(new ApiUserOutPeer()); + _users.add(new ApiUserOutPeer()); } - this.members = values.getRepeatedObj(1, _members); + this.users = values.getRepeatedObj(1, _users); this.next = values.optBytes(2); } @Override public void serialize(BserWriter writer) throws IOException { - writer.writeRepeatedObj(1, this.members); + writer.writeRepeatedObj(3, this.members); + writer.writeRepeatedObj(1, this.users); if (this.next != null) { writer.writeBytes(2, this.next); } From 5105c614034069240b361dbf32fdda81ddd1c486 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Fri, 22 Jul 2016 19:08:20 +0300 Subject: [PATCH 109/414] feat(scheme): Drop Cache update and new permissions --- actor-sdk/sdk-api/actor.json | 36 +++++++++++++++++++ .../models/im/actor/api/scheme.mps | 35 ++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/actor-sdk/sdk-api/actor.json b/actor-sdk/sdk-api/actor.json index ced200d431..f623c5dfc0 100644 --- a/actor-sdk/sdk-api/actor.json +++ b/actor-sdk/sdk-api/actor.json @@ -6601,6 +6601,32 @@ ] } }, + { + "type": "update", + "content": { + "name": "ChatDropCache", + "header": 2690, + "doc": [ + "Update about cache drop", + { + "type": "reference", + "argument": "peer", + "category": "full", + "description": " Destination peer" + } + ], + "attributes": [ + { + "type": { + "type": "struct", + "childType": "Peer" + }, + "id": 1, + "name": "peer" + } + ] + } + }, { "type": "update", "content": { @@ -7896,6 +7922,14 @@ { "name": "DELETE", "id": 4 + }, + { + "name": "JOIN", + "id": 5 + }, + { + "name": "VIEW_INFO", + "id": 6 } ] } @@ -7975,6 +8009,8 @@ "1 - canClear. Default is FALSE.", "2 - canLeave. Default is FALSE.", "3 - canDelete. Default is FALSE.", + "4 - canJoin. Default is FALSE.", + "5 - canViewInfo. Default is FALSE.", "", { "type": "reference", diff --git a/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps b/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps index c6c6aa2405..cfeca9fb2b 100644 --- a/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps +++ b/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps @@ -5878,6 +5878,27 @@ + + + + + + + + + + + + + + + + + + + + + @@ -6939,6 +6960,14 @@ + + + + + + + + @@ -7036,6 +7065,12 @@ + + + + + + From f21d274f0bbe5412d6ae5ac7f9d14fc013577816 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Fri, 22 Jul 2016 21:09:18 +0300 Subject: [PATCH 110/414] feat(core+iOS): Drop cache update support, can view and join permissions, improvements on chat deletions --- .../Resources/Base.lproj/Localizable.strings | 29 +++++---- .../Resources/es.lproj/Localizable.strings | 31 ++++----- .../Resources/pt.lproj/Localizable.strings | 31 ++++----- .../Resources/ru.lproj/Localizable.strings | 30 ++++----- .../zh-Hans.lproj/Localizable.strings | 29 ++++----- .../Compose/AAGroupCreateViewController.swift | 6 +- .../AADialogsListContentController.swift | 39 ++++++++++- .../Group/AAGroupViewController.swift | 6 +- .../main/java/im/actor/core/Messenger.java | 22 +++++-- .../actor/core/api/ApiGroupPermissions.java | 4 ++ .../actor/core/api/parser/UpdatesParser.java | 1 + .../core/api/updates/UpdateChatDropCache.java | 65 +++++++++++++++++++ .../core/modules/groups/GroupsModule.java | 17 +++-- .../modules/messaging/MessagesModule.java | 11 ++-- .../modules/messaging/MessagesProcessor.java | 2 + .../history/ConversationHistory.java | 26 ++++++++ .../history/ConversationHistoryActor.java | 41 ++++++++++-- .../modules/messaging/router/RouterActor.java | 41 +++++++++--- .../java/im/actor/core/viewmodel/GroupVM.java | 16 +++++ 19 files changed, 333 insertions(+), 114 deletions(-) create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateChatDropCache.java create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/history/ConversationHistory.java diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/Base.lproj/Localizable.strings b/actor-sdk/sdk-core-ios/ActorSDK/Resources/Base.lproj/Localizable.strings index d167990d8d..5fd957c523 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Resources/Base.lproj/Localizable.strings +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/Base.lproj/Localizable.strings @@ -333,6 +333,7 @@ "ChannelTypePrivateFull" = "Private Channel"; + "GroupShareTitle" = "Shared History"; "GroupShareEnabled" = "Shared"; @@ -344,20 +345,6 @@ "GroupShareAction" = "Share"; -"GroupDeleteTitle" = "Delete Group"; - -"GroupDeleteTitleChannel" = "Delete Channel"; - -"GroupDeleteHint" = "You will lose all messages in this group"; - -"GroupDeleteHintChannel" = "You will lose all messages in this channel"; - -"GroupDeleteMessage" = "Are you sure want to DELETE group and ALL messages in it? This action irreversible."; - -"GroupDeleteMessageChannel" = "Are you sure want to DELETE channel and ALL messages in it? This action irreversible."; - -"GroupDeleteAction" = "Delete ALL"; - "GroupEditTitle" = "Edit Group"; @@ -720,6 +707,20 @@ "ActionUnmute" = "Unmute"; +"ActionDelete" = "Delete"; + +"ActionDeleteChannel" = "Delete Channel"; + +"ActionDeleteChannelMessage" = "Wait! Deleting this channel will remove all members and all messages will be lost. Delete the channel anyway?"; + +"ActionDeleteGroup" = "Delete Group"; + +"ActionDeleteGroupMessage" = "Wait! Deleting this group will remove all members and all messages will be lost. Delete the group anyway?"; + +"ActionDeleteAndExit" = "Delete and Exit"; + +"ActionClearHistory" = "Clear History"; + /* * Network */ diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/es.lproj/Localizable.strings b/actor-sdk/sdk-core-ios/ActorSDK/Resources/es.lproj/Localizable.strings index 02ce86c036..1dfce45c36 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Resources/es.lproj/Localizable.strings +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/es.lproj/Localizable.strings @@ -344,23 +344,6 @@ "GroupShareAction" = "Share"; - -"GroupDeleteTitle" = "Delete Group"; - -"GroupDeleteTitleChannel" = "Delete Channel"; - -"GroupDeleteHint" = "You will lose all messages in this group"; - -"GroupDeleteHintChannel" = "You will lose all messages in this channel"; - -"GroupDeleteMessage" = "Are you sure want to DELETE group and ALL messages in it? This action irreversible."; - -"GroupDeleteMessageChannel" = "Are you sure want to DELETE channel and ALL messages in it? This action irreversible."; - -"GroupDeleteAction" = "Delete ALL"; - - - "GroupEditTitle" = "Edit Group"; "GroupEditTitleChannel" = "Edit Channel"; @@ -716,6 +699,20 @@ "ActionUnmute" = "Unmute"; +"ActionDelete" = "Delete"; + +"ActionDeleteChannel" = "Delete Channel"; + +"ActionDeleteChannelMessage" = "Wait! Deleting this channel will remove all members and all messages will be lost. Delete the channel anyway?"; + +"ActionDeleteGroup" = "Delete Group"; + +"ActionDeleteGroupMessage" = "Wait! Deleting this group will remove all members and all messages will be lost. Delete the group anyway?"; + +"ActionDeleteAndExit" = "Delete and Exit"; + +"ActionClearHistory" = "Clear History"; + /* * Network */ diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/pt.lproj/Localizable.strings b/actor-sdk/sdk-core-ios/ActorSDK/Resources/pt.lproj/Localizable.strings index 6bddaeda1f..c43c77c101 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Resources/pt.lproj/Localizable.strings +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/pt.lproj/Localizable.strings @@ -331,23 +331,6 @@ "GroupShareAction" = "Share"; - -"GroupDeleteTitle" = "Delete Group"; - -"GroupDeleteTitleChannel" = "Delete Channel"; - -"GroupDeleteHint" = "You will lose all messages in this group"; - -"GroupDeleteHintChannel" = "You will lose all messages in this channel"; - -"GroupDeleteMessage" = "Are you sure want to DELETE group and ALL messages in it? This action irreversible."; - -"GroupDeleteMessageChannel" = "Are you sure want to DELETE channel and ALL messages in it? This action irreversible."; - -"GroupDeleteAction" = "Delete ALL"; - - - "GroupEditTitle" = "Edit Group"; "GroupEditTitleChannel" = "Edit Channel"; @@ -698,6 +681,20 @@ "ActionUnmute" = "Unmute"; +"ActionDelete" = "Delete"; + +"ActionDeleteChannel" = "Delete Channel"; + +"ActionDeleteChannelMessage" = "Wait! Deleting this channel will remove all members and all messages will be lost. Delete the channel anyway?"; + +"ActionDeleteGroup" = "Delete Group"; + +"ActionDeleteGroupMessage" = "Wait! Deleting this group will remove all members and all messages will be lost. Delete the group anyway?"; + +"ActionDeleteAndExit" = "Delete and Exit"; + +"ActionClearHistory" = "Clear History"; + /* * Network */ diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/ru.lproj/Localizable.strings b/actor-sdk/sdk-core-ios/ActorSDK/Resources/ru.lproj/Localizable.strings index 86cf4598a0..154962d4e7 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Resources/ru.lproj/Localizable.strings +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/ru.lproj/Localizable.strings @@ -343,21 +343,6 @@ "GroupShareAction" = "Сделать Общими"; -"GroupDeleteTitle" = "Удалить группу"; - -"GroupDeleteTitleChannel" = "Удалить канал"; - -"GroupDeleteHint" = "Вы потеряете все сообщения в этой группе"; - -"GroupDeleteHintChannel" = "Вы потеряете все сообщения в этом канале"; - -"GroupDeleteMessage" = "Вы уверены что хотите УДАЛИТЬ группу и ВСЕ сообщения в ней? Это действие необратимо."; - -"GroupDeleteMessageChannel" = "Вы уверены что хотите УДАЛИТЬ канал и ВСЕ сообщения в нем? Это действие необратимо."; - -"GroupDeleteAction" = "Удалить ВСЕ"; - - "GroupEditTitle" = "Редактирование"; @@ -706,6 +691,21 @@ "ActionUnmute" = "Включить оповещения"; +"ActionDelete" = "Удалить"; + +"ActionDeleteChannel" = "Удалить канал"; + +"ActionDeleteChannelMessage" = "Стой! Удаление этого канала исключит всех подписчиков и все сообщения будут потеряны. Продолжить все равно?"; + +"ActionDeleteGroup" = "Удалить группу"; + +"ActionDeleteGroupMessage" = "Стой! Удаление этой группы исключит всех участников и все сообщения будут потеряны. Продолжить все равно?"; + +"ActionDeleteAndExit" = "Удалить и выйти"; + +"ActionClearHistory" = "Очистить историю"; + + /* * Network */ diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/zh-Hans.lproj/Localizable.strings b/actor-sdk/sdk-core-ios/ActorSDK/Resources/zh-Hans.lproj/Localizable.strings index c1cdf1b0ff..45a89ddbff 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Resources/zh-Hans.lproj/Localizable.strings +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/zh-Hans.lproj/Localizable.strings @@ -326,21 +326,6 @@ "GroupShareAction" = "Share"; -"GroupDeleteTitle" = "Delete Group"; - -"GroupDeleteTitleChannel" = "Delete Channel"; - -"GroupDeleteHint" = "You will lose all messages in this group"; - -"GroupDeleteHintChannel" = "You will lose all messages in this channel"; - -"GroupDeleteMessage" = "Are you sure want to DELETE group and ALL messages in it? This action irreversible."; - -"GroupDeleteMessageChannel" = "Are you sure want to DELETE channel and ALL messages in it? This action irreversible."; - -"GroupDeleteAction" = "Delete ALL"; - - "GroupEditTitle" = "Edit Group"; @@ -696,6 +681,20 @@ "ActionUnmute" = "Unmute"; +"ActionDelete" = "Delete"; + +"ActionDeleteChannel" = "Delete Channel"; + +"ActionDeleteChannelMessage" = "Wait! Deleting this channel will remove all members and all messages will be lost. Delete the channel anyway?"; + +"ActionDeleteGroup" = "Delete Group"; + +"ActionDeleteGroupMessage" = "Wait! Deleting this group will remove all members and all messages will be lost. Delete the group anyway?"; + +"ActionDeleteAndExit" = "Delete and Exit"; + +"ActionClearHistory" = "Clear History"; + /* * Network */ diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Compose/AAGroupCreateViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Compose/AAGroupCreateViewController.swift index 379fc5cef1..109d098d27 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Compose/AAGroupCreateViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Compose/AAGroupCreateViewController.swift @@ -140,11 +140,9 @@ public class AAGroupCreateViewController: AAViewController, UITextFieldDelegate } public override func viewWillAppear(animated: Bool) { - super.viewWillAppear(animated) - + super.viewWillAppear(animated) groupName.becomeFirstResponder() } - public func textFieldShouldReturn(textField: UITextField) -> Bool { doNext() @@ -158,6 +156,8 @@ public class AAGroupCreateViewController: AAViewController, UITextFieldDelegate return } + groupName.resignFirstResponder() + if isChannel { executePromise(Actor.createChannelWithTitle(title, withAvatar: nil)).then({ (gid: JavaLangInteger!) in self.navigateNext(AAGroupTypeViewController(gid: Int(gid.intValue()), isCreation: true), removeCurrent: true) diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Dialogs List/AADialogsListContentController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Dialogs List/AADialogsListContentController.swift index 5703108077..9a2f2a4202 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Dialogs List/AADialogsListContentController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Dialogs List/AADialogsListContentController.swift @@ -67,7 +67,44 @@ public class AADialogsListContentController: AAContentTableController, UISearchB } r.editAction = { (dialog: ACDialog) -> () in - self.executeSafe(Actor.deleteChatCommandWithPeer(dialog.peer)) + if dialog.peer.isGroup { + let g = Actor.getGroupWithGid(dialog.peer.peerId) + let isChannel = g.groupType == ACGroupType.CHANNEL() + self.alertSheet({ (a) in + + if g.isCanClear.get().booleanValue() { + a.action(AALocalized("ActionClearHistory"), closure: { + self.executeSafe(Actor.clearChatCommandWithPeer(dialog.peer)) + }) + } + + if g.isCanLeave.get().booleanValue() && g.isMember.get().booleanValue(){ + a.destructive(AALocalized("ActionDeleteAndExit"), closure: { + self.executePromise(Actor.leaveAndDeleteGroupWithGid(dialog.peer.peerId)) + }) + } else if g.isCanDelete.get().booleanValue() && g.isMember.get().booleanValue(){ + a.destructive(AALocalized(isChannel ? "ActionDeleteChannel" : "ActionDeleteGroup"), closure: { + self.executePromise(Actor.deleteGroupWithGid(dialog.peer.peerId)) + }) + } else { + a.destructive(AALocalized("ActionDelete"), closure: { + self.executeSafe(Actor.deleteChatCommandWithPeer(dialog.peer)) + }) + } + a.cancel = AALocalized("ActionCancel") + }) + + } else { + self.alertSheet({ (a) in + a.action(AALocalized("ActionClearHistory"), closure: { + self.executeSafe(Actor.clearChatCommandWithPeer(dialog.peer)) + }) + a.destructive(AALocalized("ActionDelete"), closure: { + self.executeSafe(Actor.deleteChatCommandWithPeer(dialog.peer)) + }) + a.cancel = AALocalized("ActionCancel") + }) + } } } } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewController.swift index 82e113101c..f53ab1b0df 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewController.swift @@ -310,9 +310,9 @@ public class AAGroupViewController: AAContentTableController { s.common({ (r) -> () in if self.group.groupType == ACGroupType.CHANNEL() { - r.content = AALocalized("GroupLeaveChannel") + r.content = AALocalized("ChannelLeave") } else { - r.content = AALocalized("GroupLeave") + r.content = AALocalized("ActionDeleteAndExit") } r.style = .Destructive @@ -325,7 +325,7 @@ public class AAGroupViewController: AAContentTableController { title = AALocalized("GroupLeaveConfirm") } self.confirmDestructive(title, action: AALocalized("GroupLeaveConfirmAction"), yes: { () -> () in - self.executeSafe(Actor.leaveGroupCommandWithGid(jint(self.gid))) + self.executePromise(Actor.leaveAndDeleteGroupWithGid(jint(self.gid))) }) return true diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java index 14ee5f3cec..1a908adb93 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java @@ -1544,14 +1544,24 @@ public Promise createChannel(String title, String avatarDescriptor) { * Leave group * * @param gid group's id - * @return Command for execution + * @return Promise of Void */ @NotNull - @ObjectiveCName("leaveGroupCommandWithGid:") - public Command leaveGroup(final int gid) { - return callback -> modules.getGroupsModule().leaveGroup(gid) - .then(v -> callback.onResult(v)) - .failure(e -> callback.onError(e)); + @ObjectiveCName("leaveGroupWithGid:") + public Promise leaveGroup(final int gid) { + return modules.getGroupsModule().leaveGroup(gid); + } + + /** + * Leave and delete group + * + * @param gid group's id + * @return Promise of Void + */ + @NotNull + @ObjectiveCName("leaveAndDeleteGroupWithGid:") + public Promise leaveAndDeleteGroup(int gid) { + return modules.getGroupsModule().leaveAndDeleteGroup(gid); } /** diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiGroupPermissions.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiGroupPermissions.java index 9679c52923..cc67d1d30e 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiGroupPermissions.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiGroupPermissions.java @@ -11,6 +11,8 @@ public enum ApiGroupPermissions { CLEAR(2), LEAVE(3), DELETE(4), + JOIN(5), + VIEW_INFO(6), UNSUPPORTED_VALUE(-1); private int value; @@ -29,6 +31,8 @@ public static ApiGroupPermissions parse(int value) throws IOException { case 2: return ApiGroupPermissions.CLEAR; case 3: return ApiGroupPermissions.LEAVE; case 4: return ApiGroupPermissions.DELETE; + case 5: return ApiGroupPermissions.JOIN; + case 6: return ApiGroupPermissions.VIEW_INFO; default: return ApiGroupPermissions.UNSUPPORTED_VALUE; } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/parser/UpdatesParser.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/parser/UpdatesParser.java index 1f1dcd4a0e..5bd8ea0008 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/parser/UpdatesParser.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/parser/UpdatesParser.java @@ -47,6 +47,7 @@ public Update read(int type, byte[] payload) throws IOException { case 47: return UpdateChatClear.fromBytes(payload); case 48: return UpdateChatDelete.fromBytes(payload); case 94: return UpdateChatArchive.fromBytes(payload); + case 2690: return UpdateChatDropCache.fromBytes(payload); case 1: return UpdateChatGroupsChanged.fromBytes(payload); case 222: return UpdateReactionsUpdate.fromBytes(payload); case 2609: return UpdateGroupTitleChanged.fromBytes(payload); diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateChatDropCache.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateChatDropCache.java new file mode 100644 index 0000000000..9a3e82b244 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateChatDropCache.java @@ -0,0 +1,65 @@ +package im.actor.core.api.updates; +/* + * Generated by the Actor API Scheme generator. DO NOT EDIT! + */ + +import im.actor.runtime.bser.*; +import im.actor.runtime.collections.*; +import static im.actor.runtime.bser.Utils.*; +import im.actor.core.network.parser.*; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.NotNull; +import com.google.j2objc.annotations.ObjectiveCName; +import java.io.IOException; +import java.util.List; +import java.util.ArrayList; +import im.actor.core.api.*; + +public class UpdateChatDropCache extends Update { + + public static final int HEADER = 0xa82; + public static UpdateChatDropCache fromBytes(byte[] data) throws IOException { + return Bser.parse(new UpdateChatDropCache(), data); + } + + private ApiPeer peer; + + public UpdateChatDropCache(@NotNull ApiPeer peer) { + this.peer = peer; + } + + public UpdateChatDropCache() { + + } + + @NotNull + public ApiPeer getPeer() { + return this.peer; + } + + @Override + public void parse(BserValues values) throws IOException { + this.peer = values.getObj(1, new ApiPeer()); + } + + @Override + public void serialize(BserWriter writer) throws IOException { + if (this.peer == null) { + throw new IOException(); + } + writer.writeObject(1, this.peer); + } + + @Override + public String toString() { + String res = "update ChatDropCache{"; + res += "peer=" + this.peer; + res += "}"; + return res; + } + + @Override + public int getHeaderKey() { + return HEADER; + } +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsModule.java index 4e7fbb0e55..919d036e0f 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsModule.java @@ -25,6 +25,7 @@ import im.actor.core.api.rpc.RequestInviteUser; import im.actor.core.api.rpc.RequestJoinGroup; import im.actor.core.api.rpc.RequestKickUser; +import im.actor.core.api.rpc.RequestLeaveAndDelete; import im.actor.core.api.rpc.RequestLeaveGroup; import im.actor.core.api.rpc.RequestLoadAdminSettings; import im.actor.core.api.rpc.RequestLoadMembers; @@ -171,7 +172,7 @@ public Promise addMember(final int gid, final int uid) { .flatMap(r -> updates().waitForUpdate(r.getSeq())); } - public Promise kickMember(final int gid, final int uid) { + public Promise kickMember(int gid, int uid) { final long rid = RandomUtils.nextRid(); return Promises.tuple(getGroups().getValueAsync(gid), users().getValueAsync(uid)) .flatMap(groupUserTuple2 -> @@ -183,7 +184,7 @@ public Promise kickMember(final int gid, final int uid) { .flatMap(r -> updates().waitForUpdate(r.getSeq())); } - public Promise leaveGroup(final int gid) { + public Promise leaveGroup(int gid) { final long rid = RandomUtils.nextRid(); return getGroups().getValueAsync(gid) .flatMap(group -> @@ -194,6 +195,14 @@ public Promise leaveGroup(final int gid) { .flatMap(r -> updates().waitForUpdate(r.getSeq())); } + public Promise leaveAndDeleteGroup(int gid) { + return getGroups().getValueAsync(gid) + .flatMap(group -> + api(new RequestLeaveAndDelete( + new ApiGroupOutPeer(group.getGroupId(), group.getAccessHash())))) + .flatMap(r -> updates().waitForUpdate(r.getSeq())); + } + public Promise deleteGroup(int gid) { return getGroups().getValueAsync(gid) .flatMap(group -> @@ -284,10 +293,10 @@ public Promise loadMembers(int gid, int limit, byte[] next) { api(new RequestLoadMembers( new ApiGroupOutPeer(group.getGroupId(), group.getAccessHash()), limit, next))) - .chain(r -> updates().loadRequiredPeers(r.getMembers(), new ArrayList<>())) + .chain(r -> updates().loadRequiredPeers(r.getUsers(), new ArrayList<>())) .map(r -> { ArrayList members = new ArrayList<>(); - for (ApiUserOutPeer p : r.getMembers()) { + for (ApiUserOutPeer p : r.getUsers()) { members.add(p.getUid()); } return new GroupMembersSlice(members, r.getNext()); diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/MessagesModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/MessagesModule.java index a7b49d824b..7cc463e32b 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/MessagesModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/MessagesModule.java @@ -56,6 +56,7 @@ import im.actor.core.modules.messaging.actions.CursorReaderActor; import im.actor.core.modules.messaging.actions.CursorReceiverActor; import im.actor.core.modules.messaging.dialogs.DialogsActor; +import im.actor.core.modules.messaging.history.ConversationHistory; import im.actor.core.modules.messaging.history.ConversationHistoryActor; import im.actor.core.modules.messaging.history.DialogsHistoryActor; import im.actor.core.modules.messaging.actions.MessageDeleteActor; @@ -105,7 +106,7 @@ public class MessagesModule extends AbsModule implements BusSubscriber { private ActorRef sendMessageActor; private ActorRef deletionsActor; private RouterInt router; - private final HashMap historyLoaderActors = new HashMap<>(); + private final HashMap historyLoaderActors = new HashMap<>(); private MVVMCollection conversationStates; @@ -159,12 +160,10 @@ public ActorRef getPlainReceiverActor() { return plainReceiverActor; } - public ActorRef getHistoryActor(final Peer peer) { + public ConversationHistory getHistoryActor(final Peer peer) { synchronized (historyLoaderActors) { if (!historyLoaderActors.containsKey(peer)) { - historyLoaderActors.put(peer, system().actorOf("history/" + peer, () -> { - return new ConversationHistoryActor(peer, context()); - })); + historyLoaderActors.put(peer, new ConversationHistory(peer, context())); } return historyLoaderActors.get(peer); } @@ -448,7 +447,7 @@ public void loadMoreArchivedDialogs(final boolean init, final RpcCallback getHistoryActor(peer).send(new ConversationHistoryActor.LoadMore())); + im.actor.runtime.Runtime.dispatch(() -> getHistoryActor(peer).loadMore()); } // diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/MessagesProcessor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/MessagesProcessor.java index 45536fd91f..0a400982a8 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/MessagesProcessor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/MessagesProcessor.java @@ -13,6 +13,7 @@ import im.actor.core.api.ApiPeer; import im.actor.core.api.updates.UpdateChatClear; import im.actor.core.api.updates.UpdateChatDelete; +import im.actor.core.api.updates.UpdateChatDropCache; import im.actor.core.api.updates.UpdateChatGroupsChanged; import im.actor.core.api.updates.UpdateMessage; import im.actor.core.api.updates.UpdateMessageContentChanged; @@ -97,6 +98,7 @@ public Promise process(Update update) { update instanceof UpdateMessageContentChanged || update instanceof UpdateChatClear || update instanceof UpdateChatDelete || + update instanceof UpdateChatDropCache || update instanceof UpdateChatGroupsChanged || update instanceof UpdateReactionsUpdate || update instanceof UpdateMessageSent) { diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/history/ConversationHistory.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/history/ConversationHistory.java new file mode 100644 index 0000000000..8ece2d4eaa --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/history/ConversationHistory.java @@ -0,0 +1,26 @@ +package im.actor.core.modules.messaging.history; + +import im.actor.core.entity.Peer; +import im.actor.core.modules.ModuleContext; +import im.actor.runtime.actors.ActorInterface; +import im.actor.runtime.actors.messages.Void; +import im.actor.runtime.promise.Promise; + +import static im.actor.runtime.actors.ActorSystem.system; + +public class ConversationHistory extends ActorInterface { + + public ConversationHistory(Peer peer, ModuleContext context) { + setDest(system().actorOf("history/" + peer, () -> { + return new ConversationHistoryActor(peer, context); + })); + } + + public void loadMore() { + send(new ConversationHistoryActor.LoadMore()); + } + + public Promise reset() { + return ask(new ConversationHistoryActor.Reset()); + } +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/history/ConversationHistoryActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/history/ConversationHistoryActor.java index b76d4d000e..4cceb5cdea 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/history/ConversationHistoryActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/history/ConversationHistoryActor.java @@ -21,8 +21,10 @@ import im.actor.core.modules.api.ApiSupportConfiguration; import im.actor.core.modules.ModuleContext; import im.actor.core.modules.ModuleActor; +import im.actor.runtime.actors.ask.AskMessage; import im.actor.runtime.actors.messages.Void; import im.actor.runtime.function.Consumer; +import im.actor.runtime.promise.Promise; public class ConversationHistoryActor extends ModuleActor { @@ -39,7 +41,7 @@ public class ConversationHistoryActor extends ModuleActor { private long historyMaxDate; private boolean historyLoaded; - private boolean isLoading = false; + private boolean isFreezed = false; public ConversationHistoryActor(Peer peer, ModuleContext context) { super(context); @@ -60,15 +62,27 @@ public void preStart() { } private void onLoadMore() { - if (isLoading || historyLoaded) { + if (isFreezed || historyLoaded) { return; } - isLoading = true; + isFreezed = true; api(new RequestLoadHistory(buidOutPeer(peer), historyMaxDate, null, LIMIT, ApiSupportConfiguration.OPTIMIZATIONS)) .chain(r -> updates().applyRelatedData(r.getUsers(), r.getGroups())) .chain(r -> updates().loadRequiredPeers(r.getUserPeers(), r.getGroupPeers())) .then(applyHistory(peer)) - .then(responseLoadHistory -> isLoading = false); + .then(responseLoadHistory -> { + isFreezed = false; + unstashAll(); + }); + } + + private void onReset() { + historyMaxDate = 0; + preferences().putLong(KEY_LOADED_DATE, Long.MAX_VALUE); + historyLoaded = false; + preferences().putBool(KEY_LOADED, false); + preferences().putBool(KEY_LOADED_INIT, false); + self().send(new LoadMore()); } private Consumer applyHistory(final Peer peer) { @@ -122,6 +136,21 @@ private void applyHistory(Peer peer, List history) { }); } + + @Override + public Promise onAsk(Object message) throws Exception { + if (message instanceof Reset) { + if (isFreezed) { + stash(); + return null; + } + onReset(); + return Promise.success(null); + } else { + return super.onAsk(message); + } + } + @Override public void onReceive(Object message) { if (message instanceof LoadMore) { @@ -134,4 +163,8 @@ public void onReceive(Object message) { public static class LoadMore { } + + public static class Reset implements AskMessage { + + } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java index 5ffc05e8a9..fffd5922b0 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java @@ -11,6 +11,7 @@ import im.actor.core.api.rpc.RequestLoadGroupedDialogs; import im.actor.core.api.updates.UpdateChatClear; import im.actor.core.api.updates.UpdateChatDelete; +import im.actor.core.api.updates.UpdateChatDropCache; import im.actor.core.api.updates.UpdateChatGroupsChanged; import im.actor.core.api.updates.UpdateMessage; import im.actor.core.api.updates.UpdateMessageContentChanged; @@ -519,6 +520,21 @@ private Promise onChatClear(Peer peer) { return getDialogsRouter().onChatClear(peer); } + private Promise onChatDropCache(Peer peer) { + return context().getMessagesModule().getHistoryActor(peer).reset() + .flatMap(r -> { + conversation(peer).clear(); + + ConversationState state = conversationStates.getValue(peer.getUnuqueId()); + state = state.changeIsLoaded(false); + conversationStates.addOrUpdateItem(state); + + updateChatState(peer); + + return getDialogsRouter().onChatClear(peer); + }); + } + private Promise onChatDelete(Peer peer) { conversation(peer).clear(); @@ -799,7 +815,7 @@ public Promise onUpdate(Update update) { context().getMessagesModule() .getSendMessageActor() .send(new SenderActor.MessageSent(peer, messageSent.getRid())); - onOutgoingSent( + return onOutgoingSent( peer, messageSent.getRid(), messageSent.getDate()); @@ -809,7 +825,7 @@ public Promise onUpdate(Update update) { UpdateMessageRead read = (UpdateMessageRead) update; Peer peer = convert(read.getPeer()); if (isValidPeer(peer)) { - onMessageRead(peer, read.getStartDate()); + return onMessageRead(peer, read.getStartDate()); } return Promise.success(null); } else if (update instanceof UpdateMessageReadByMe) { @@ -820,28 +836,35 @@ public Promise onUpdate(Update update) { if (readByMe.getUnreadCounter() != null) { counter = readByMe.getUnreadCounter(); } - onMessageReadByMe(peer, readByMe.getStartDate(), counter); + return onMessageReadByMe(peer, readByMe.getStartDate(), counter); } return Promise.success(null); } else if (update instanceof UpdateMessageReceived) { UpdateMessageReceived received = (UpdateMessageReceived) update; Peer peer = convert(received.getPeer()); if (isValidPeer(peer)) { - onMessageReceived(peer, received.getStartDate()); + return onMessageReceived(peer, received.getStartDate()); } return Promise.success(null); } else if (update instanceof UpdateChatDelete) { UpdateChatDelete delete = (UpdateChatDelete) update; Peer peer = convert(delete.getPeer()); if (isValidPeer(peer)) { - onChatDelete(peer); + return onChatDelete(peer); } return Promise.success(null); } else if (update instanceof UpdateChatClear) { UpdateChatClear clear = (UpdateChatClear) update; Peer peer = convert(clear.getPeer()); if (isValidPeer(peer)) { - onChatClear(peer); + return onChatClear(peer); + } + return Promise.success(null); + } else if (update instanceof UpdateChatDropCache) { + UpdateChatDropCache dropCache = (UpdateChatDropCache) update; + Peer peer = convert(dropCache.getPeer()); + if (isValidPeer(peer)) { + return onChatDropCache(peer); } return Promise.success(null); } else if (update instanceof UpdateChatGroupsChanged) { @@ -852,7 +875,7 @@ public Promise onUpdate(Update update) { UpdateMessageDelete delete = (UpdateMessageDelete) update; Peer peer = convert(delete.getPeer()); if (isValidPeer(peer)) { - onMessageDeleted(peer, delete.getRids()); + return onMessageDeleted(peer, delete.getRids()); } return Promise.success(null); } else if (update instanceof UpdateMessageContentChanged) { @@ -860,7 +883,7 @@ public Promise onUpdate(Update update) { Peer peer = convert(contentChanged.getPeer()); if (isValidPeer(peer)) { AbsContent content = AbsContent.fromMessage(contentChanged.getMessage()); - onContentUpdate(peer, contentChanged.getRid(), content); + return onContentUpdate(peer, contentChanged.getRid(), content); } return Promise.success(null); } else if (update instanceof UpdateReactionsUpdate) { @@ -871,7 +894,7 @@ public Promise onUpdate(Update update) { for (ApiMessageReaction r : reactionsUpdate.getReactions()) { reactions.add(new Reaction(r.getCode(), r.getUsers())); } - onReactionsUpdate(peer, reactionsUpdate.getRid(), reactions); + return onReactionsUpdate(peer, reactionsUpdate.getRid(), reactions); } return Promise.success(null); } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java index fe5e5abd46..e832636411 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java @@ -56,6 +56,9 @@ public class GroupVM extends BaseValueModel { @NotNull @Property("nonatomic, readonly") private BooleanValueModel isCanCall; + @NotNull + @Property("nonatomic, readonly") + private BooleanValueModel isCanClear; @NotNull @Property("nonatomic, readonly") @@ -159,6 +162,7 @@ public GroupVM(@NotNull Group rawObj) { this.isCanEditForeign = new BooleanValueModel("group." + groupId + ".isCanEditForeign", rawObj.isCanEditForeign()); this.isCanDeleteForeign = new BooleanValueModel("group." + groupId + ".isCanDeleteForeign", rawObj.isCanDeleteForeign()); this.isDeleted = new BooleanValueModel("group." + groupId + ".isDeleted", rawObj.isDeleted()); + this.isCanClear = new BooleanValueModel("group." + groupId + ".isCanClear", rawObj.isCanClear()); this.ownerId = new IntValueModel("group." + groupId + ".membersCount", rawObj.getOwnerId()); this.members = new ValueModel<>("group." + groupId + ".members", new HashSet<>(rawObj.getMembers())); @@ -432,6 +436,17 @@ public BooleanValueModel getIsCanDeleteForeign() { return isCanDeleteForeign; } + /** + * Is current user can clear messages + * + * @return is current user can clear messages model + */ + @NotNull + @ObjectiveCName("getIsCanClearModel") + public BooleanValueModel getIsCanClear() { + return isCanClear; + } + /** * Is group deleted * @@ -553,6 +568,7 @@ protected void updateValues(@NotNull Group rawObj) { isChanged |= isCanEditForeign.change(rawObj.isCanEditForeign()); isChanged |= isCanDeleteForeign.change(rawObj.isCanDeleteForeign()); isChanged |= isDeleted.change(rawObj.isDeleted()); + isChanged |= isCanClear.change(rawObj.isCanClear()); if (isChanged) { notifyIfNeeded(); From 818335ac5ccd28eba38da002327a7b36a945d5e4 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Fri, 22 Jul 2016 21:27:04 +0300 Subject: [PATCH 111/414] fix(iOS): Fixing localization --- .../ActorSDK/Resources/es.lproj/Localizable.strings | 2 +- .../Sources/Controllers/Root/AARootTabViewController.swift | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/es.lproj/Localizable.strings b/actor-sdk/sdk-core-ios/ActorSDK/Resources/es.lproj/Localizable.strings index 976879373a..9ccd8d46a2 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Resources/es.lproj/Localizable.strings +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/es.lproj/Localizable.strings @@ -419,7 +419,7 @@ "Placeholder_Empty_Title" = "Invita a tus amigos"; -"Placeholder_Empty_Message" = "Ninguno de tus contactos usar {appname}. Utiliza el botón de abajo para invitarlos."; +"Placeholder_Empty_Message" = "Ninguno de tus contactos usan {appname}. Utiliza el botón de abajo para invitarlos."; "Placeholder_Empty_Action" = "CONTAR A UN AMIGO"; diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Root/AARootTabViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Root/AARootTabViewController.swift index 3665e74b74..dd2c6c6174 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Root/AARootTabViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Root/AARootTabViewController.swift @@ -90,9 +90,10 @@ public class AARootTabViewController : UITabBarController, MFMessageComposeViewC } messageComposeController.body = AALocalized("InviteText") .replace("{link}", dest: ActorSDK.sharedActor().inviteUrl) + .replace("{appname}", dest: ActorSDK.sharedActor().appName) messageComposeController.navigationBar.tintColor = ActorSDK.sharedActor().style.navigationTitleColor presentViewController(messageComposeController, animated: true, completion: { () -> Void in -// ActorSDK.sharedActor().style.appl + }) } else { UIAlertView(title: "Error", message: "Cannot send SMS", delegate: nil, cancelButtonTitle: "OK").show() From 1a1b24776dfee9e9b3f13417d6762a8a29f13995 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Sun, 24 Jul 2016 20:28:59 +0300 Subject: [PATCH 112/414] feat(iOS): Handling deletion --- .../Resources/Base.lproj/Localizable.strings | 52 ++++++++++------- .../Resources/es.lproj/Localizable.strings | 48 +++++++++------- .../Resources/pt.lproj/Localizable.strings | 52 ++++++++++------- .../Resources/ru.lproj/Localizable.strings | 57 +++++++++++++++---- .../zh-Hans.lproj/Localizable.strings | 53 +++++++++++------ .../AADialogsListContentController.swift | 34 ++++++++--- .../ConversationViewController.swift | 8 +++ .../AAGroupAdministrationViewController.swift | 6 +- .../Group/AAGroupViewController.swift | 11 ++-- .../Controllers/Managed Runtime/Alerts.swift | 8 +++ 10 files changed, 223 insertions(+), 106 deletions(-) diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/Base.lproj/Localizable.strings b/actor-sdk/sdk-core-ios/ActorSDK/Resources/Base.lproj/Localizable.strings index 5fd957c523..6e630e3c83 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Resources/Base.lproj/Localizable.strings +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/Base.lproj/Localizable.strings @@ -294,6 +294,8 @@ "GroupDescription" = "Description"; + + "GroupAdministration" = "Administration"; "GroupTypeTitle" = "Group Type"; @@ -332,6 +334,10 @@ "ChannelTypePrivateFull" = "Private Channel"; +"GroupDeleteHint" = "You will lose all messages in this group"; + +"GroupDeleteHintChannel" = "You will lose all messages in this channel"; + "GroupShareTitle" = "Shared History"; @@ -385,15 +391,6 @@ "GroupAddParticipantUrl" = "Invite to Group via Link"; -"GroupLeave" = "Leave group"; - -"GroupLeaveChannel" = "Leave channel"; - -"GroupLeaveConfirm" = "Are you sure you want to leave group?"; - -"GroupLeaveConfirmChannel" = "Are you sure you want to leave channel?"; - -"GroupLeaveConfirmAction" = "Leave"; "GroupInviteLinkPageTitle" = "Invite Link"; @@ -405,24 +402,12 @@ "GroupInviteLinkRevokeAction" = "Revoke"; -"GroupIntegrationPageTitle" = "Integration API"; -"GroupIntegrationLinkTitle" = "LINK"; - -"GroupIntegrationLinkHint" = "You can use this link to send a message from any external system."; - -"GroupIntegrationDoc" = "Open Documentation"; - -"GroupIntegrationLinkRevokeMessage" = "Are you sure you want to revoke this link? Once you do, no one will be able to send messages to the group using it."; - -"GroupIntegrationLinkRevokeAction" = "Revoke"; "GroupJoinMessage" = "Are you sure you want to join this group?"; "GroupJoinAction" = "Join"; -"GroupMembers" = "MEMBERS"; - /* * Compose */ @@ -480,6 +465,8 @@ "Chat.MicrophoneAccessDisabled" = "We need access to your microphone for voice messages. Please go to Settings — Privacy — Microphone turn them ОN"; +"ChatDeleted" = "This chat was deleted by owner."; + /* * Chat attachment menu */ @@ -707,8 +694,12 @@ "ActionUnmute" = "Unmute"; + "ActionDelete" = "Delete"; +"ActionDeleteMessage" = "Are you sure want to delete chat?"; + + "ActionDeleteChannel" = "Delete Channel"; "ActionDeleteChannelMessage" = "Wait! Deleting this channel will remove all members and all messages will be lost. Delete the channel anyway?"; @@ -717,10 +708,29 @@ "ActionDeleteGroupMessage" = "Wait! Deleting this group will remove all members and all messages will be lost. Delete the group anyway?"; + +"ActionLeaveChannel" = "Leave Channel"; + +"ActionLeaveChannelMessage" = "Are you sure want to leave channel?"; + +"ActionLeaveChannelAction" = "Leave"; + + + "ActionDeleteAndExit" = "Delete and Exit"; +"ActionDeleteAndExitMessage" = "Are you sure want to exit group and delete all messages?"; + +"ActionDeleteAndExitAction" = "Exit"; + + + "ActionClearHistory" = "Clear History"; +"ActionClearHistoryMessage" = "Are you sure want to clear history?"; + +"ActionClearHistoryAction" = "Clear"; + /* * Network */ diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/es.lproj/Localizable.strings b/actor-sdk/sdk-core-ios/ActorSDK/Resources/es.lproj/Localizable.strings index 1dfce45c36..8022278eef 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Resources/es.lproj/Localizable.strings +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/es.lproj/Localizable.strings @@ -291,9 +291,9 @@ "GroupDescription" = "Description"; -"GroupAdministration" = "Administration"; +"GroupAdministration" = "Administration"; "GroupTypeTitle" = "Group Type"; @@ -331,6 +331,10 @@ "GroupTypeLinkHintChannel" = "People can share this link with others and find your channel using search"; +"GroupDeleteHint" = "You will lose all messages in this group"; + +"GroupDeleteHintChannel" = "You will lose all messages in this channel"; + "GroupShareTitle" = "Shared History"; @@ -384,15 +388,7 @@ "GroupAddParticipantUrl" = "Invitar al grupo a través de Enlace"; -"GroupLeave" = "Salir de grupo"; - -"GroupLeaveChannel" = "Leave channel"; - -"GroupLeaveConfirm" = "¿Seguro que quieres dejar de grupo?"; - -"GroupLeaveConfirmChannel" = "Are you sure you want to leave channel?"; -"GroupLeaveConfirmAction" = "Salir"; "GroupInviteLinkPageTitle" = "invitar por Enlace"; @@ -404,17 +400,7 @@ "GroupInviteLinkRevokeAction" = "Revocar"; -"GroupIntegrationPageTitle" = "Integración de API"; -"GroupIntegrationLinkTitle" = "LINK"; - -"GroupIntegrationLinkHint" = "Puedes usar este enlace para enviar mensajes desde cualquier sistema externo."; - -"GroupIntegrationDoc" = "Abrir Documentación"; - -"GroupIntegrationLinkRevokeMessage" = "¿Seguro que quieres revocar este enlace? Una vez hecho, nadie va a ser capaz de enviar mensajes al grupo de usarlo."; - -"GroupIntegrationLinkRevokeAction" = "Revocar"; "GroupJoinMessage" = "¿Seguro quieres unirte al grupo?"; @@ -476,6 +462,7 @@ "ChatSlideUpToCancel" = "Deslizar para cancelar"; +"ChatDeleted" = "This chat was deleted by owner."; /* * Chat attachment menu @@ -699,8 +686,12 @@ "ActionUnmute" = "Unmute"; + "ActionDelete" = "Delete"; +"ActionDeleteMessage" = "Are you sure want to delete chat?"; + + "ActionDeleteChannel" = "Delete Channel"; "ActionDeleteChannelMessage" = "Wait! Deleting this channel will remove all members and all messages will be lost. Delete the channel anyway?"; @@ -709,10 +700,29 @@ "ActionDeleteGroupMessage" = "Wait! Deleting this group will remove all members and all messages will be lost. Delete the group anyway?"; + +"ActionLeaveChannel" = "Leave Channel"; + +"ActionLeaveChannelMessage" = "Are you sure want to leave channel?"; + +"ActionLeaveChannelAction" = "Leave"; + + + "ActionDeleteAndExit" = "Delete and Exit"; +"ActionDeleteAndExitMessage" = "Are you sure want to exit group and delete all messages?"; + +"ActionDeleteAndExitAction" = "Exit"; + + + "ActionClearHistory" = "Clear History"; +"ActionClearHistoryMessage" = "Are you sure want to clear history?"; + +"ActionClearHistoryAction" = "Clear"; + /* * Network */ diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/pt.lproj/Localizable.strings b/actor-sdk/sdk-core-ios/ActorSDK/Resources/pt.lproj/Localizable.strings index c43c77c101..90350beeeb 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Resources/pt.lproj/Localizable.strings +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/pt.lproj/Localizable.strings @@ -280,6 +280,8 @@ "GroupDescription" = "Description"; + + "GroupAdministration" = "Administration"; "GroupTypeTitle" = "Group Type"; @@ -318,6 +320,9 @@ "GroupTypeLinkHintChannel" = "People can share this link with others and find your channel using search"; +"GroupDeleteHint" = "You will lose all messages in this group"; + +"GroupDeleteHintChannel" = "You will lose all messages in this channel"; "GroupShareTitle" = "Shared History"; @@ -373,15 +378,7 @@ "GroupAddParticipantUrl" = "COnvidar para o grupo via Link"; -"GroupLeave" = "Sair do grupo"; - -"GroupLeaveChannel" = "Leave channel"; -"GroupLeaveConfirm" = "Você tem certeza que quer sair do grupo?"; - -"GroupLeaveConfirmChannel" = "Are you sure you want to leave channel?"; - -"GroupLeaveConfirmAction" = "Sair"; "GroupInviteLinkPageTitle" = "Link de Convite"; @@ -393,20 +390,6 @@ "GroupInviteLinkRevokeAction" = "Remover"; -"GroupIntegrationPageTitle" = "Integração API"; - -"GroupIntegrationLinkTitle" = "LINK"; - -"GroupIntegrationLinkHint" = "Você pode usar este link para enviar mesnagens para qualquer sistema exteno."; - -"GroupIntegrationDoc" = "Abrir Documentação"; - -"GroupIntegrationLinkRevokeMessage" = "Você tem certeza que quer remover este link? Uma vez removido, mais ninguém sera capaz de enviar mensagens para o grupo usando este link."; - -"GroupIntegrationLinkRevokeAction" = "Remover"; - -"GroupMembers" = "MEMBERS"; - /* * Compose */ @@ -437,6 +420,8 @@ "CreateGroupMembersPlaceholders" = "Enter Names"; +"ChatDeleted" = "This chat was deleted by owner."; + /* * Location */ @@ -681,8 +666,12 @@ "ActionUnmute" = "Unmute"; + "ActionDelete" = "Delete"; +"ActionDeleteMessage" = "Are you sure want to delete chat?"; + + "ActionDeleteChannel" = "Delete Channel"; "ActionDeleteChannelMessage" = "Wait! Deleting this channel will remove all members and all messages will be lost. Delete the channel anyway?"; @@ -691,10 +680,29 @@ "ActionDeleteGroupMessage" = "Wait! Deleting this group will remove all members and all messages will be lost. Delete the group anyway?"; + +"ActionLeaveChannel" = "Leave Channel"; + +"ActionLeaveChannelMessage" = "Are you sure want to leave channel?"; + +"ActionLeaveChannelAction" = "Leave"; + + + "ActionDeleteAndExit" = "Delete and Exit"; +"ActionDeleteAndExitMessage" = "Are you sure want to exit group and delete all messages?"; + +"ActionDeleteAndExitAction" = "Exit"; + + + "ActionClearHistory" = "Clear History"; +"ActionClearHistoryMessage" = "Are you sure want to clear history?"; + +"ActionClearHistoryAction" = "Clear"; + /* * Network */ diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/ru.lproj/Localizable.strings b/actor-sdk/sdk-core-ios/ActorSDK/Resources/ru.lproj/Localizable.strings index 154962d4e7..51c7880d5c 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Resources/ru.lproj/Localizable.strings +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/ru.lproj/Localizable.strings @@ -293,6 +293,8 @@ "GroupDescription" = "Описание"; + + "GroupAdministration" = "Администрирование"; "GroupTypeTitle" = "Тип группы"; @@ -331,6 +333,12 @@ "GroupTypeLinkHintChannel" = "Ваши друзья могут делиться этой ссылкой или находить канал в поиске"; +"GroupDeleteHint" = "Вы потеряете всех сообщения в этой группе"; + +"GroupDeleteHintChannel" = "Вы потеряете все сообщения в этом канале"; + + + "GroupShareTitle" = "Общая история"; @@ -404,24 +412,12 @@ "GroupInviteLinkRevokeAction" = "Сбросить"; -"GroupIntegrationPageTitle" = "API Интеграции"; - -"GroupIntegrationLinkTitle" = "ССЫЛКА"; - -"GroupIntegrationLinkHint" = "Вы можете использовать эту ссылку для отправки сообщений из любых внешних сервисов."; - -"GroupIntegrationDoc" = "Открыть документацию"; - -"GroupIntegrationLinkRevokeMessage" = "Вы уверены, что хотите сбросить ссылку? Как только вы это сделаете, вы больше не сможете по ней отправлять сообщения в группу."; -"GroupIntegrationLinkRevokeAction" = "Сбросить"; "GroupJoinMessage" = "Вы уверены, что хотите вступить в группу?"; "GroupJoinAction" = "Вступить"; -"GroupMembers" = "УЧАСТНИКИ"; - /* * Compose */ @@ -468,6 +464,8 @@ "ChatNoGroupAccess" = "Вы не состоите в группе"; +"ChatDeleted" = "Этот чат был удален владельцем."; + /* * Chat attachment menu */ @@ -706,6 +704,41 @@ "ActionClearHistory" = "Очистить историю"; + +"ActionDelete" = "Удалить"; + +"ActionDeleteMessage" = "Вы уверены что хотите удалить чат?"; + + +"ActionDeleteChannel" = "Удалть канал"; + +"ActionDeleteChannelMessage" = "Стой! Удаление этого канала исключит всех подписчиков и все сообщения будут потеряны. Продолжить все равно?"; + +"ActionDeleteGroup" = "Удалить группу"; + +"ActionDeleteGroupMessage" = "Стой! Удаление этой группы исключит всех участников и все сообщения будут потеряны. Продолжить все равно?"; + + +"ActionLeaveChannel" = "Покинуть канал"; + +"ActionLeaveChannelMessage" = "Вы уверены, что хотите выйти из канала?"; + +"ActionLeaveChannelAction" = "Выйти"; + + +"ActionDeleteAndExit" = "Удалить и выйти"; + +"ActionDeleteAndExitMessage" = "Вы уверены, что хотите хотите выйти из группы и удалить все сообщения?"; + +"ActionDeleteAndExitAction" = "Выйти"; + + +"ActionClearHistory" = "Очистить историю"; + +"ActionClearHistoryMessage" = "Вы уверены что хотите очистить историю?"; + +"ActionClearHistoryAction" = "Очистить"; + /* * Network */ diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/zh-Hans.lproj/Localizable.strings b/actor-sdk/sdk-core-ios/ActorSDK/Resources/zh-Hans.lproj/Localizable.strings index 45a89ddbff..c75ddd7660 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Resources/zh-Hans.lproj/Localizable.strings +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/zh-Hans.lproj/Localizable.strings @@ -280,6 +280,8 @@ "GroupDescription" = "Description"; + + "GroupAdministration" = "Administration"; "GroupTypeTitle" = "Group Type"; @@ -314,6 +316,12 @@ "GroupTypeLinkHintChannel" = "People can share this link with others and find your channel using search"; +"GroupDeleteHint" = "You will lose all messages in this group"; + +"GroupDeleteHintChannel" = "You will lose all messages in this channel"; + + + "GroupShareTitle" = "Shared History"; @@ -363,13 +371,7 @@ "GroupAddParticipantUrl" = "通过URL链接邀请加入群组"; -"GroupLeave" = "退出群组"; - -"GroupLeaveChannel" = "Leave channel"; - -"GroupLeaveConfirm" = "你确定要退出群组吗?"; -"GroupLeaveConfirmAction" = "退出"; "GroupInviteLinkPageTitle" = "邀请链接"; @@ -381,24 +383,12 @@ "GroupInviteLinkRevokeAction" = "重置"; -"GroupIntegrationPageTitle" = "集成API"; -"GroupIntegrationLinkTitle" = "链接"; - -"GroupIntegrationLinkHint" = "任何外部系统都可以利用这个链接向群组发送消息。"; - -"GroupIntegrationDoc" = "打开文档"; - -"GroupIntegrationLinkRevokeMessage" = "确定要重置这个链接,重置后其他系统将无法使用这个链接发送消息。"; - -"GroupIntegrationLinkRevokeAction" = "重置"; "GroupJoinMessage" = "确定要加入群组吗?"; "GroupJoinAction" = "加入"; -"GroupMembers" = "群组成员"; - /* * Compose */ @@ -456,6 +446,8 @@ "Chat.MicrophoneAccessDisabled" = "我们需要访问麦克风来发送语音信息. 请打开 设置 — 隐私 — 麦克风,然后打开开关"; +"ChatDeleted" = "This chat was deleted by owner."; + /* * Chat attachment menu */ @@ -681,8 +673,13 @@ "ActionUnmute" = "Unmute"; + + "ActionDelete" = "Delete"; +"ActionDeleteMessage" = "Are you sure want to delete chat?"; + + "ActionDeleteChannel" = "Delete Channel"; "ActionDeleteChannelMessage" = "Wait! Deleting this channel will remove all members and all messages will be lost. Delete the channel anyway?"; @@ -691,10 +688,30 @@ "ActionDeleteGroupMessage" = "Wait! Deleting this group will remove all members and all messages will be lost. Delete the group anyway?"; + +"ActionLeaveChannel" = "Leave Channel"; + +"ActionLeaveChannelMessage" = "Are you sure want to leave channel?"; + +"ActionLeaveChannelAction" = "Leave"; + + + "ActionDeleteAndExit" = "Delete and Exit"; +"ActionDeleteAndExitMessage" = "Are you sure want to exit group and delete all messages?"; + +"ActionDeleteAndExitAction" = "Exit"; + + + "ActionClearHistory" = "Clear History"; +"ActionClearHistoryMessage" = "Are you sure want to clear history?"; + +"ActionClearHistoryAction" = "Clear"; + + /* * Network */ diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Dialogs List/AADialogsListContentController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Dialogs List/AADialogsListContentController.swift index 9a2f2a4202..0b7b77c5fd 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Dialogs List/AADialogsListContentController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Dialogs List/AADialogsListContentController.swift @@ -72,25 +72,45 @@ public class AADialogsListContentController: AAContentTableController, UISearchB let isChannel = g.groupType == ACGroupType.CHANNEL() self.alertSheet({ (a) in + // Clear History if g.isCanClear.get().booleanValue() { a.action(AALocalized("ActionClearHistory"), closure: { - self.executeSafe(Actor.clearChatCommandWithPeer(dialog.peer)) + self.confirmAlertUserDanger("ActionClearHistoryMessage", action: "ActionClearHistoryAction", tapYes: { + self.executeSafe(Actor.clearChatCommandWithPeer(dialog.peer)) + }) }) } - if g.isCanLeave.get().booleanValue() && g.isMember.get().booleanValue(){ - a.destructive(AALocalized("ActionDeleteAndExit"), closure: { - self.executePromise(Actor.leaveAndDeleteGroupWithGid(dialog.peer.peerId)) - }) + // Delete + if g.isCanLeave.get().booleanValue() && g.isMember.get().booleanValue() { + if isChannel { + a.destructive(AALocalized("ActionLeaveChannel"), closure: { + self.confirmAlertUserDanger("ActionLeaveChannelMessage", action: "ActionLeaveChannelAction", tapYes: { + self.executePromise(Actor.leaveAndDeleteGroupWithGid(dialog.peer.peerId)) + }) + }) + } else { + a.destructive(AALocalized("ActionDeleteAndExit"), closure: { + self.confirmAlertUserDanger("ActionDeleteAndExitMessage", action: "ActionDeleteAndExitAction", tapYes: { + self.executePromise(Actor.leaveAndDeleteGroupWithGid(dialog.peer.peerId)) + }) + }) + } } else if g.isCanDelete.get().booleanValue() && g.isMember.get().booleanValue(){ a.destructive(AALocalized(isChannel ? "ActionDeleteChannel" : "ActionDeleteGroup"), closure: { - self.executePromise(Actor.deleteGroupWithGid(dialog.peer.peerId)) + self.confirmAlertUserDanger(isChannel ? "ActionDeleteChannelMessage" : "ActionDeleteGroupMessage", action: "ActionDelete", tapYes: { + self.executePromise(Actor.deleteGroupWithGid(g.groupId)) + }) }) } else { a.destructive(AALocalized("ActionDelete"), closure: { - self.executeSafe(Actor.deleteChatCommandWithPeer(dialog.peer)) + self.confirmAlertUserDanger("ActionDeleteMessage", action: "ActionDelete", tapYes: { + self.executeSafe(Actor.deleteChatCommandWithPeer(dialog.peer)) + }) }) } + + // Cancel a.cancel = AALocalized("ActionCancel") }) diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Conversation/ConversationViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Conversation/ConversationViewController.swift index 9ec90e563d..f5bc14cbd1 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Conversation/ConversationViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Conversation/ConversationViewController.swift @@ -399,6 +399,14 @@ public class ConversationViewController: self.inputOverlay.hidden = false } } + + binder.bind(group.isDeleted) { (isDeleted: JavaLangBoolean!) in + if isDeleted.booleanValue() { + self.alertUser("ChatDeleted") { + self.navigateBack() + } + } + } } Actor.onConversationOpenWithPeer(peer) diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupAdministrationViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupAdministrationViewController.swift index 70e789f1a7..898d6c5a8f 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupAdministrationViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupAdministrationViewController.swift @@ -90,15 +90,15 @@ public class AAGroupAdministrationViewController: AAContentTableController { section { (s) in let action: String if isChannel { + action = AALocalized("ActionDeleteChannel") s.footerText = AALocalized("GroupDeleteHintChannel") - action = AALocalized("GroupDeleteTitleChannel") } else { + action = AALocalized("ActionDeleteGroup") s.footerText = AALocalized("GroupDeleteHint") - action = AALocalized("GroupDeleteTitle") } s.danger(action, closure: { (r) in r.selectAction = { () -> Bool in - self.confirmAlertUserDanger(self.isChannel ? "GroupDeleteMessageChannel" : "GroupDeleteMessage", action: "GroupDeleteAction", tapYes: { + self.confirmAlertUserDanger(self.isChannel ? "ActionDeleteChannelMessage" : "ActionDeleteGroupMessage", action: "ActionDelete", tapYes: { self.executePromise(Actor.deleteGroupWithGid(jint(self.gid))).after { let first = self.navigationController!.viewControllers.first! self.navigationController!.setViewControllers([first], animated: true) diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewController.swift index f53ab1b0df..828dfdc178 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewController.swift @@ -310,7 +310,7 @@ public class AAGroupViewController: AAContentTableController { s.common({ (r) -> () in if self.group.groupType == ACGroupType.CHANNEL() { - r.content = AALocalized("ChannelLeave") + r.content = AALocalized("ActionLeaveChannel") } else { r.content = AALocalized("ActionDeleteAndExit") } @@ -319,12 +319,15 @@ public class AAGroupViewController: AAContentTableController { r.selectAction = { () -> Bool in let title: String + let action: String if self.group.groupType == ACGroupType.CHANNEL() { - title = AALocalized("GroupLeaveConfirmChannel") + title = AALocalized("ActionLeaveChannelMessage") + action = AALocalized("ActionLeaveChannelAction") } else { - title = AALocalized("GroupLeaveConfirm") + title = AALocalized("ActionDeleteAndExitMessage") + action = AALocalized("ActionDeleteAndExitMessageAction") } - self.confirmDestructive(title, action: AALocalized("GroupLeaveConfirmAction"), yes: { () -> () in + self.confirmDestructive(title, action: action, yes: { () -> () in self.executePromise(Actor.leaveAndDeleteGroupWithGid(jint(self.gid))) }) diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/Alerts.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/Alerts.swift index b2c444b2f5..97a1f6d288 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/Alerts.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/Alerts.swift @@ -15,6 +15,14 @@ public extension UIViewController { self.presentViewController(controller, animated: true, completion: nil) } + public func alertUser(message: String, tapYes: ()->()) { + let controller = UIAlertController(title: nil, message: message, preferredStyle: UIAlertControllerStyle.Alert) + controller.addAction(UIAlertAction(title: AALocalized("AlertOk"), style: UIAlertActionStyle.Cancel, handler: { (alertView) -> () in + tapYes() + })) + self.presentViewController(controller, animated: true, completion: nil) + } + public func confirmAlertUser(message: String, action: String, tapYes: ()->(), tapNo: (()->())? = nil) { let controller = UIAlertController(title: nil, message: AALocalized(message), preferredStyle: UIAlertControllerStyle.Alert) controller.addAction(UIAlertAction(title: AALocalized(action), style: UIAlertActionStyle.Default, handler: { (alertView) -> () in From 453f29c215625c7225ab06f155c6a19d9d48931b Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Sun, 24 Jul 2016 21:25:36 +0300 Subject: [PATCH 113/414] feat(iOS): Showing async members --- .../dialogs/DialogsDefaultFragment.java | 15 +- .../ActorSDK.xcodeproj/project.pbxproj | 4 + .../ConversationViewController.swift | 6 +- .../Group/AAGroupViewController.swift | 2 +- .../Group/AAGroupViewMembersController.swift | 154 ++++++++++++++++++ .../Managed Runtime/ManagedCells.swift | 10 +- .../actor/core/entity/GroupMembersSlice.java | 16 +- .../core/modules/groups/GroupsModule.java | 10 +- 8 files changed, 192 insertions(+), 25 deletions(-) create mode 100644 actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewMembersController.swift diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/DialogsDefaultFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/DialogsDefaultFragment.java index b64b9b6bdf..205a0a74ff 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/DialogsDefaultFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/DialogsDefaultFragment.java @@ -88,18 +88,9 @@ public void onError(Exception e) { .setMessage(getString(R.string.alert_leave_group_message, dialog.getDialogTitle())) .setNegativeButton(R.string.dialog_cancel, null) .setPositiveButton(R.string.alert_leave_group_yes, (d1, which1) -> { - execute(messenger().leaveGroup(dialog.getPeer().getPeerId()), R.string.progress_common, - new CommandCallback() { - @Override - public void onResult(Void res) { - - } - - @Override - public void onError(Exception e) { - Toast.makeText(getActivity(), R.string.toast_unable_leave, Toast.LENGTH_LONG).show(); - } - }); + execute(messenger().leaveGroup(dialog.getPeer().getPeerId()), R.string.progress_common).failure(e -> { + Toast.makeText(getActivity(), R.string.toast_unable_leave, Toast.LENGTH_LONG).show(); + }); }) .show(); } else { diff --git a/actor-sdk/sdk-core-ios/ActorSDK.xcodeproj/project.pbxproj b/actor-sdk/sdk-core-ios/ActorSDK.xcodeproj/project.pbxproj index ae2aefbaa5..f096cb3892 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK.xcodeproj/project.pbxproj +++ b/actor-sdk/sdk-core-ios/ActorSDK.xcodeproj/project.pbxproj @@ -224,6 +224,7 @@ 066A53371BC537CA000E606E /* ConversationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 066A53361BC537CA000E606E /* ConversationViewController.swift */; }; 066A53391BC5456B000E606E /* ActorStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 066A53381BC5456B000E606E /* ActorStyle.swift */; }; 066CBCDC1C8D419F004507E2 /* AAAuthEmailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 066CBCDB1C8D419F004507E2 /* AAAuthEmailViewController.swift */; }; + 067B67541D45341D00B9A238 /* AAGroupViewMembersController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 067B67531D45341D00B9A238 /* AAGroupViewMembersController.swift */; }; 068AFC391C94A0050055F503 /* AADialogListProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 068AFC381C94A0050055F503 /* AADialogListProcessor.swift */; }; 069CF4CC1BCB909A00C66E12 /* CLBackspaceDetectingTextField.h in Headers */ = {isa = PBXBuildFile; fileRef = 069CF4C41BCB909A00C66E12 /* CLBackspaceDetectingTextField.h */; }; 069CF4CD1BCB909A00C66E12 /* CLBackspaceDetectingTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = 069CF4C51BCB909A00C66E12 /* CLBackspaceDetectingTextField.m */; }; @@ -601,6 +602,7 @@ 066A53361BC537CA000E606E /* ConversationViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConversationViewController.swift; sourceTree = ""; }; 066A53381BC5456B000E606E /* ActorStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActorStyle.swift; sourceTree = ""; }; 066CBCDB1C8D419F004507E2 /* AAAuthEmailViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AAAuthEmailViewController.swift; sourceTree = ""; }; + 067B67531D45341D00B9A238 /* AAGroupViewMembersController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AAGroupViewMembersController.swift; sourceTree = ""; }; 068AFC381C94A0050055F503 /* AADialogListProcessor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AADialogListProcessor.swift; sourceTree = ""; }; 069CF4C41BCB909A00C66E12 /* CLBackspaceDetectingTextField.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CLBackspaceDetectingTextField.h; sourceTree = ""; }; 069CF4C51BCB909A00C66E12 /* CLBackspaceDetectingTextField.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CLBackspaceDetectingTextField.m; sourceTree = ""; }; @@ -1240,6 +1242,7 @@ 066A52E81BC52A25000E606E /* Cells */, 066A52E21BC52A20000E606E /* AAGroupViewController.swift */, 06ABFE371D3FCCE30031A0D6 /* AAGroupEditInfoViewController.swift */, + 067B67531D45341D00B9A238 /* AAGroupViewMembersController.swift */, 06ABFE3C1D41283A0031A0D6 /* AAGroupAdministrationViewController.swift */, 06ABFE391D410D2D0031A0D6 /* AAGroupTypeController.swift */, 066A52E31BC52A20000E606E /* AAAddParticipantViewController.swift */, @@ -2289,6 +2292,7 @@ 066A51901BC4C383000E606E /* CocoaNetworkRuntime.swift in Sources */, 06ABFE381D3FCCE30031A0D6 /* AAGroupEditInfoViewController.swift in Sources */, 06E7B24A1C0F92140090660C /* AABubbleLocationCell.swift in Sources */, + 067B67541D45341D00B9A238 /* AAGroupViewMembersController.swift in Sources */, 066A52581BC4EF61000E606E /* Alerts.swift in Sources */, 066A51691BC4C366000E606E /* AATools.swift in Sources */, 066A53201BC533F5000E606E /* AABubbleBackgroundProcessor.swift in Sources */, diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Conversation/ConversationViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Conversation/ConversationViewController.swift index f5bc14cbd1..d012a60974 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Conversation/ConversationViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Conversation/ConversationViewController.swift @@ -402,8 +402,10 @@ public class ConversationViewController: binder.bind(group.isDeleted) { (isDeleted: JavaLangBoolean!) in if isDeleted.booleanValue() { - self.alertUser("ChatDeleted") { - self.navigateBack() + self.alertUser(AALocalized("ChatDeleted")) { + self.execute(Actor.deleteChatCommandWithPeer(self.peer), successBlock: { (r) in + self.navigateBack() + }) } } } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewController.swift index 828dfdc178..4c1eeecddb 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewController.swift @@ -160,7 +160,7 @@ public class AAGroupViewController: AAContentTableController { r.content = AALocalized("GroupViewMembers") r.style = .Normal r.selectAction = { () -> Bool in - // TODO: Implement + self.navigateNext(AAGroupViewMembersController(gid: self.gid)) return false } }) diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewMembersController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewMembersController.swift new file mode 100644 index 0000000000..85ab8c92f8 --- /dev/null +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewMembersController.swift @@ -0,0 +1,154 @@ +// +// Copyright (c) 2014-2016 Actor LLC. +// + +import Foundation + +public class AAGroupViewMembersController: AAContentTableController { + + private var membersRow: AAManagedArrayRows! + + private var isLoaded = false + private var isLoading = false + private var next: IOSByteArray! = nil + + public init(gid: Int) { + super.init(style: .Plain) + self.gid = gid + + navigationItem.title = AALocalized("GroupMembers") + + if group.isCanInviteMembers.get().booleanValue() { + navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.Add, target: self, action: #selector(didAddPressed)) + } + } + + public required init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public override func tableDidLoad() { + section { (s) in + self.membersRow = s.arrays({ (r: AAManagedArrayRows) -> () in + r.height = 48 + r.data = [ACGroupMember]() + + r.bindData = { (c, d) -> () in + let user = Actor.getUserWithUid(d.uid) + c.bind(user, isAdmin: d.isAdministrator) + + // Notify to request onlines + Actor.onUserVisibleWithUid(d.uid) + } + + r.itemShown = { (index, d) in + if index > r.data.count - 10 { + self.loadMore() + } + } + + r.selectAction = { (d) -> Bool in + let user = Actor.getUserWithUid(d.uid) + if (user.getId() == Actor.myUid()) { + return true + } + + self.alertSheet { (a: AAAlertSetting) -> () in + + a.cancel = "AlertCancel" + + a.action("GroupMemberInfo") { () -> () in + var controller: AAViewController! = ActorSDK.sharedActor().delegate.actorControllerForUser(Int(user.getId())) + if controller == nil { + controller = AAUserViewController(uid: Int(user.getId())) + } + self.navigateNext(controller, removeCurrent: false) + } + + a.action("GroupMemberWrite") { () -> () in + if let customController = ActorSDK.sharedActor().delegate.actorControllerForConversation(ACPeer.userWithInt(user.getId())) { + self.navigateDetail(customController) + } else { + self.navigateDetail(ConversationViewController(peer: ACPeer.userWithInt(user.getId()))) + } + self.popover?.dismissPopoverAnimated(true) + } + + a.action("GroupMemberCall", closure: { () -> () in + let phones = user.getPhonesModel().get() + if phones.size() == 0 { + self.alertUser("GroupMemberCallNoPhones") + } else if phones.size() == 1 { + let number = phones.getWithInt(0) + ActorSDK.sharedActor().openUrl("telprompt://+\(number.phone)") + } else { + + var numbers = [String]() + for i in 0.. () in + if (index >= 0) { + let number = phones.getWithInt(jint(index)) + ActorSDK.sharedActor().openUrl("telprompt://+\(number.phone)") + } + }) + } + }) + + // Can kick user + let canKick: Bool = + (self.group.isCanKickAnyone.get().booleanValue() || + (self.group.isCanKickInvited.get().booleanValue() && d.inviterUid == Actor.myUid())) + + if canKick { + let name = Actor.getUserWithUid(d.uid).getNameModel().get() + a.destructive("GroupMemberKick") { () -> () in + self.confirmDestructive(AALocalized("GroupMemberKickMessage") + .replace("{name}", dest: name), action: AALocalized("GroupMemberKickAction")) { + self.executeSafe(Actor.kickMemberCommandWithGid(jint(self.gid), withUid: user.getId())) + } + } + } + } + + return true + } + }) + } + + loadMore() + } + + private func loadMore() { + if isLoading { + return + } + if isLoaded { + return + } + + isLoading = true + Actor.loadMembersWithGid(jint(gid), withLimit: 20, withNext: next).then { (slice: ACGroupMembersSlice!) in + for i in 0..: AAManagedRange { public var bindData: ((cell: R, item: T) -> ())? + public var itemShown: ((index: Int, item: T) -> ())? + public var data = [T]() public func initTable(table: AAManagedTable) { @@ -670,7 +672,13 @@ public class AAManagedArrayRows: AAManagedRange { public func rangeCellForItem(table: AAManagedTable, indexPath: AARangeIndexPath) -> UITableViewCell { let res: R = table.dequeueCell(indexPath.indexPath) - rangeBindData(table, indexPath: indexPath, cell: res, item: data[indexPath.item]) + let item = data[indexPath.item] + rangeBindData(table, indexPath: indexPath, cell: res, item: item) + + if let shown = itemShown { + shown(index: indexPath.item, item: item) + } + return res } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/GroupMembersSlice.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/GroupMembersSlice.java index b8e0571eaa..6eb17d2497 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/GroupMembersSlice.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/GroupMembersSlice.java @@ -1,19 +1,23 @@ package im.actor.core.entity; +import com.google.j2objc.annotations.Property; + import java.util.ArrayList; public class GroupMembersSlice { - - private ArrayList uids; + + @Property("readonly, nonatomic") + private ArrayList members; + @Property("readonly, nonatomic") private byte[] next; - public GroupMembersSlice(ArrayList uids, byte[] next) { - this.uids = uids; + public GroupMembersSlice(ArrayList members, byte[] next) { + this.members = members; this.next = next; } - public ArrayList getUids() { - return uids; + public ArrayList getMembers() { + return members; } public byte[] getNext() { diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsModule.java index 919d036e0f..e7117ea1cb 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsModule.java @@ -11,6 +11,7 @@ import im.actor.core.api.ApiAdminSettings; import im.actor.core.api.ApiGroupOutPeer; import im.actor.core.api.ApiGroupType; +import im.actor.core.api.ApiMember; import im.actor.core.api.ApiOutPeer; import im.actor.core.api.ApiPeerType; import im.actor.core.api.ApiUserOutPeer; @@ -39,6 +40,7 @@ import im.actor.core.api.rpc.ResponseInviteUrl; import im.actor.core.api.rpc.ResponseVoid; import im.actor.core.entity.Group; +import im.actor.core.entity.GroupMember; import im.actor.core.entity.GroupMembersSlice; import im.actor.core.entity.GroupPermissions; import im.actor.core.entity.Peer; @@ -295,9 +297,11 @@ public Promise loadMembers(int gid, int limit, byte[] next) { limit, next))) .chain(r -> updates().loadRequiredPeers(r.getUsers(), new ArrayList<>())) .map(r -> { - ArrayList members = new ArrayList<>(); - for (ApiUserOutPeer p : r.getUsers()) { - members.add(p.getUid()); + ArrayList members = new ArrayList<>(); + for (ApiMember p : r.getMembers()) { + boolean isAdmin = p.isAdmin() != null ? p.isAdmin() : false; + members.add(new GroupMember(p.getUid(), + p.getInviterUid(), p.getInviterUid(), isAdmin)); } return new GroupMembersSlice(members, r.getNext()); }); From 657ca94e57677d45d6d969c0bb516a44decdb394 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Sun, 24 Jul 2016 21:46:07 +0300 Subject: [PATCH 114/414] feat(ios+core): canJoin support --- .../ConversationViewController.swift | 2 ++ .../Group/AAGroupViewMembersController.swift | 2 +- .../main/java/im/actor/core/entity/Group.java | 14 +++++++++ .../java/im/actor/core/viewmodel/GroupVM.java | 31 +++++++++++++++++++ 4 files changed, 48 insertions(+), 1 deletion(-) diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Conversation/ConversationViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Conversation/ConversationViewController.swift index d012a60974..a8fa83740f 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Conversation/ConversationViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Conversation/ConversationViewController.swift @@ -379,6 +379,8 @@ public class ConversationViewController: } }) +// binder.bind(group.isMember, valueModel2: group.isCanWriteMessage, valueModel3: group.isCanJ, closure: <#T##(value1: T1!, value2: T2!, value3: T3!) -> ()#>) + binder.bind(group.isMember, valueModel2: group.isCanWriteMessage) { (isMember: JavaLangBoolean!, canWriteMessage: JavaLangBoolean!) in if canWriteMessage.booleanValue() { self.stickersButton.hidden = false diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewMembersController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewMembersController.swift index 85ab8c92f8..523ba3afaa 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewMembersController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewMembersController.swift @@ -16,7 +16,7 @@ public class AAGroupViewMembersController: AAContentTableController { super.init(style: .Plain) self.gid = gid - navigationItem.title = AALocalized("GroupMembers") + navigationItem.title = AALocalized("GroupViewMembers") if group.isCanInviteMembers.get().booleanValue() { navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.Add, target: self, action: #selector(didAddPressed)) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Group.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Group.java index 39603d0061..29f7939272 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Group.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Group.java @@ -64,6 +64,10 @@ public class Group extends WrapperExtEntity implements K private boolean isCanDelete; @Property("readonly, nonatomic") private boolean isDeleted; + @Property("readonly, nonatomic") + private boolean isCanJoin; + @Property("readonly, nonatomic") + private boolean isCanViewInfo; @NotNull @Property("readonly, nonatomic") @SuppressWarnings("NullableProblems") @@ -194,6 +198,14 @@ public boolean isCanCall() { return isCanCall; } + public boolean isCanJoin() { + return isCanJoin; + } + + public boolean isCanViewInfo() { + return isCanViewInfo; + } + @NotNull public GroupType getGroupType() { return groupType; @@ -767,6 +779,8 @@ protected void applyWrapped(@NotNull ApiGroup wrapped, @Nullable ApiGroupFull ex this.isCanClear = BitMaskUtil.getBitValue(permissions, ApiGroupPermissions.CLEAR); this.isCanLeave = BitMaskUtil.getBitValue(permissions, ApiGroupPermissions.LEAVE); this.isCanDelete = BitMaskUtil.getBitValue(permissions, ApiGroupPermissions.DELETE); + this.isCanJoin = BitMaskUtil.getBitValue(permissions, ApiGroupPermissions.JOIN); + this.isCanViewInfo = BitMaskUtil.getBitValue(permissions, ApiGroupPermissions.VIEW_INFO); // // Ext diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java index e832636411..f8cb56194a 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java @@ -59,6 +59,12 @@ public class GroupVM extends BaseValueModel { @NotNull @Property("nonatomic, readonly") private BooleanValueModel isCanClear; + @NotNull + @Property("nonatomic, readonly") + private BooleanValueModel isCanJoin; + @NotNull + @Property("nonatomic, readonly") + private BooleanValueModel isCanViewInfo; @NotNull @Property("nonatomic, readonly") @@ -163,6 +169,8 @@ public GroupVM(@NotNull Group rawObj) { this.isCanDeleteForeign = new BooleanValueModel("group." + groupId + ".isCanDeleteForeign", rawObj.isCanDeleteForeign()); this.isDeleted = new BooleanValueModel("group." + groupId + ".isDeleted", rawObj.isDeleted()); this.isCanClear = new BooleanValueModel("group." + groupId + ".isCanClear", rawObj.isCanClear()); + this.isCanJoin = new BooleanValueModel("group." + groupId + ".isCanJoin", rawObj.isCanJoin()); + this.isCanViewInfo = new BooleanValueModel("group." + groupId + ".isCanViewInfo", rawObj.isCanViewInfo()); this.ownerId = new IntValueModel("group." + groupId + ".membersCount", rawObj.getOwnerId()); this.members = new ValueModel<>("group." + groupId + ".members", new HashSet<>(rawObj.getMembers())); @@ -447,6 +455,27 @@ public BooleanValueModel getIsCanClear() { return isCanClear; } + /** + * Is current user can view info + * + * @return is current user can view info model + */ + @NotNull + @ObjectiveCName("getIsCanViewInfoModel") + public BooleanValueModel getIsCanViewInfo() { + return isCanViewInfo; + } + + /** + * Is current user can join + * + * @return is current user can join model + */ + @NotNull + public BooleanValueModel getIsCanJoin() { + return isCanJoin; + } + /** * Is group deleted * @@ -569,6 +598,8 @@ protected void updateValues(@NotNull Group rawObj) { isChanged |= isCanDeleteForeign.change(rawObj.isCanDeleteForeign()); isChanged |= isDeleted.change(rawObj.isDeleted()); isChanged |= isCanClear.change(rawObj.isCanClear()); + isChanged |= isCanViewInfo.change(rawObj.isCanViewInfo()); + isChanged |= isCanJoin.change(rawObj.isCanJoin()); if (isChanged) { notifyIfNeeded(); From 935ad76328c72a3501653b808ac28d55bac14627 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Sun, 24 Jul 2016 21:46:25 +0300 Subject: [PATCH 115/414] fix(iOS+core): Missing changes --- .../Resources/Base.lproj/Localizable.strings | 2 ++ .../Resources/es.lproj/Localizable.strings | 2 ++ .../Resources/pt.lproj/Localizable.strings | 4 ++++ .../Resources/ru.lproj/Localizable.strings | 2 ++ .../zh-Hans.lproj/Localizable.strings | 2 ++ .../ConversationViewController.swift | 20 +++++++++++++------ .../core/modules/groups/GroupsModule.java | 1 - 7 files changed, 26 insertions(+), 7 deletions(-) diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/Base.lproj/Localizable.strings b/actor-sdk/sdk-core-ios/ActorSDK/Resources/Base.lproj/Localizable.strings index 6e630e3c83..80c8361c52 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Resources/Base.lproj/Localizable.strings +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/Base.lproj/Localizable.strings @@ -467,6 +467,8 @@ "ChatDeleted" = "This chat was deleted by owner."; +"ChatJoin" = "Join"; + /* * Chat attachment menu */ diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/es.lproj/Localizable.strings b/actor-sdk/sdk-core-ios/ActorSDK/Resources/es.lproj/Localizable.strings index 8022278eef..d7249c346a 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Resources/es.lproj/Localizable.strings +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/es.lproj/Localizable.strings @@ -464,6 +464,8 @@ "ChatDeleted" = "This chat was deleted by owner."; +"ChatJoin" = "Join"; + /* * Chat attachment menu */ diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/pt.lproj/Localizable.strings b/actor-sdk/sdk-core-ios/ActorSDK/Resources/pt.lproj/Localizable.strings index 90350beeeb..06cb435186 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Resources/pt.lproj/Localizable.strings +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/pt.lproj/Localizable.strings @@ -448,6 +448,10 @@ "ChatNoGroupAccess" = "Você não é membro"; +"ChatDeleted" = "This chat was deleted by owner."; + +"ChatJoin" = "Join"; + /* * Find */ diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/ru.lproj/Localizable.strings b/actor-sdk/sdk-core-ios/ActorSDK/Resources/ru.lproj/Localizable.strings index 51c7880d5c..df6bd18933 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Resources/ru.lproj/Localizable.strings +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/ru.lproj/Localizable.strings @@ -466,6 +466,8 @@ "ChatDeleted" = "Этот чат был удален владельцем."; +"ChatJoin" = "Присоединиться"; + /* * Chat attachment menu */ diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/zh-Hans.lproj/Localizable.strings b/actor-sdk/sdk-core-ios/ActorSDK/Resources/zh-Hans.lproj/Localizable.strings index c75ddd7660..912b4f75c2 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Resources/zh-Hans.lproj/Localizable.strings +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/zh-Hans.lproj/Localizable.strings @@ -448,6 +448,8 @@ "ChatDeleted" = "This chat was deleted by owner."; +"ChatJoin" = "Join"; + /* * Chat attachment menu */ diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Conversation/ConversationViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Conversation/ConversationViewController.swift index a8fa83740f..f5769435d5 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Conversation/ConversationViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Conversation/ConversationViewController.swift @@ -379,15 +379,18 @@ public class ConversationViewController: } }) -// binder.bind(group.isMember, valueModel2: group.isCanWriteMessage, valueModel3: group.isCanJ, closure: <#T##(value1: T1!, value2: T2!, value3: T3!) -> ()#>) - - binder.bind(group.isMember, valueModel2: group.isCanWriteMessage) { (isMember: JavaLangBoolean!, canWriteMessage: JavaLangBoolean!) in + binder.bind(group.isMember, valueModel2: group.isCanWriteMessage, valueModel3: group.isCanJoin, closure: { (isMember: JavaLangBoolean!, canWriteMessage: JavaLangBoolean!, canJoin: JavaLangBoolean!) in + if canWriteMessage.booleanValue() { self.stickersButton.hidden = false self.inputOverlay.hidden = true } else { if !isMember.booleanValue() { - self.inputOverlayLabel.text = AALocalized("ChatNoGroupAccess") + if canJoin.booleanValue() { + self.inputOverlayLabel.text = AALocalized("ChatJoin") + } else { + self.inputOverlayLabel.text = AALocalized("ChatNoGroupAccess") + } } else { if Actor.isNotificationsEnabledWithPeer(self.peer) { self.inputOverlayLabel.text = AALocalized("ActionMute") @@ -400,7 +403,8 @@ public class ConversationViewController: self.textInputbar.textView.text = "" self.inputOverlay.hidden = false } - } + }) + binder.bind(group.isDeleted) { (isDeleted: JavaLangBoolean!) in if isDeleted.booleanValue() { @@ -429,7 +433,11 @@ public class ConversationViewController: if peer.isGroup { let group = Actor.getGroupWithGid(peer.peerId) if !group.isMember.get().booleanValue() { - // DO NOTHING + if group.isCanJoin.get().booleanValue() { + // T + } else { + // DO NOTHING + } } else if !group.isCanWriteMessage.get().booleanValue() { if Actor.isNotificationsEnabledWithPeer(peer) { Actor.changeNotificationsEnabledWithPeer(peer, withValue: false) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsModule.java index e7117ea1cb..c1c2fd83b5 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsModule.java @@ -343,7 +343,6 @@ public Promise joinGroupByToken(final String token) { .map(responseJoinGroup -> responseJoinGroup.getGroup().getId()); } - // // Integration Token // From fd8bda79559319c2391eab04f603406d744dc2b5 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Sun, 24 Jul 2016 21:51:25 +0300 Subject: [PATCH 116/414] feat(scheme): Join specific group --- actor-sdk/sdk-api/actor.json | 30 +++++++++++++++++++ .../models/im/actor/api/scheme.mps | 24 +++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/actor-sdk/sdk-api/actor.json b/actor-sdk/sdk-api/actor.json index f623c5dfc0..f3497d9ae5 100644 --- a/actor-sdk/sdk-api/actor.json +++ b/actor-sdk/sdk-api/actor.json @@ -10220,6 +10220,36 @@ ] } }, + { + "type": "rpc", + "content": { + "name": "JoinGroupByPeer", + "header": 2722, + "response": { + "type": "reference", + "name": "Seq" + }, + "doc": [ + "Join group by peer", + { + "type": "reference", + "argument": "groupPeer", + "category": "full", + "description": "Group's peer" + } + ], + "attributes": [ + { + "type": { + "type": "struct", + "childType": "GroupOutPeer" + }, + "id": 1, + "name": "groupPeer" + } + ] + } + }, { "type": "comment", "content": "Administration" diff --git a/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps b/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps index cfeca9fb2b..2416c8a7c5 100644 --- a/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps +++ b/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps @@ -8953,6 +8953,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + From 97b6b9da2cab728dc36314e85978779126548218 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Mon, 25 Jul 2016 17:57:10 +0300 Subject: [PATCH 117/414] fix(core): Fixing dropping chat cache --- actor-sdk/sdk-api/actor.json | 2 +- .../models/im/actor/api/scheme.mps | 2 +- .../Conversation/Cell/AABubbleCell.swift | 2 - .../ConversationViewController.swift | 2 +- .../main/java/im/actor/core/Messenger.java | 12 ++++ .../im/actor/core/api/parser/RpcParser.java | 1 + .../core/api/rpc/RequestJoinGroupByPeer.java | 65 +++++++++++++++++++ .../api/updates/UpdateChatGroupsChanged.java | 2 +- .../core/modules/groups/GroupsModule.java | 10 +++ .../history/ConversationHistoryActor.java | 35 ++++++---- .../modules/messaging/router/RouterActor.java | 29 ++++++--- .../modules/messaging/router/RouterInt.java | 5 ++ .../router/entity/RouterResetChat.java | 18 +++++ .../sequence/SequenceHandlerActor.java | 5 +- 14 files changed, 163 insertions(+), 27 deletions(-) create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/RequestJoinGroupByPeer.java create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/entity/RouterResetChat.java diff --git a/actor-sdk/sdk-api/actor.json b/actor-sdk/sdk-api/actor.json index f3497d9ae5..7316b4f0d6 100644 --- a/actor-sdk/sdk-api/actor.json +++ b/actor-sdk/sdk-api/actor.json @@ -6637,7 +6637,7 @@ { "type": "reference", "argument": "dialogs", - "category": "full", + "category": "compact", "description": " New dialgos list" } ], diff --git a/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps b/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps index 2416c8a7c5..39bb9626af 100644 --- a/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps +++ b/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps @@ -5917,7 +5917,7 @@ - + diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Cell/AABubbleCell.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Cell/AABubbleCell.swift index 294eed0f81..98954e7c0a 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Cell/AABubbleCell.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Cell/AABubbleCell.swift @@ -179,8 +179,6 @@ public class AABubbleCell: UICollectionViewCell { contentView.transform = CGAffineTransformMake(1, 0, 0, -1, 0, 0) - ActorSDK.sharedActor().style.bubbleShadowEnabled ? contentView.addSubview(bubbleShadow) : print("go to light!") - contentView.addSubview(bubble) contentView.addSubview(bubbleBorder) contentView.addSubview(newMessage) diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Conversation/ConversationViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Conversation/ConversationViewController.swift index f5769435d5..9fe626f1ad 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Conversation/ConversationViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Conversation/ConversationViewController.swift @@ -434,7 +434,7 @@ public class ConversationViewController: let group = Actor.getGroupWithGid(peer.peerId) if !group.isMember.get().booleanValue() { if group.isCanJoin.get().booleanValue() { - // T + executePromise(Actor.joinGroupWithGid(peer.peerId)) } else { // DO NOTHING } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java index 1a908adb93..73c3f9a40c 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java @@ -1701,6 +1701,18 @@ public Command joinGroupViaToken(String token) { .failure(e -> callback.onError(e)); } + /** + * Join group + * + * @param gid group's id + * @return Promise of Void + */ + @NotNull + @ObjectiveCName("joinGroupWithGid:") + public Promise joinGroup(int gid) { + return modules.getGroupsModule().joinGroup(gid); + } + /** * Request integration token for group * diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/parser/RpcParser.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/parser/RpcParser.java index 709b69ab59..e628db24b1 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/parser/RpcParser.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/parser/RpcParser.java @@ -89,6 +89,7 @@ public RpcScope read(int type, byte[] payload) throws IOException { case 70: return RequestLeaveGroup.fromBytes(payload); case 2721: return RequestLeaveAndDelete.fromBytes(payload); case 71: return RequestKickUser.fromBytes(payload); + case 2722: return RequestJoinGroupByPeer.fromBytes(payload); case 2784: return RequestMakeUserAdmin.fromBytes(payload); case 2791: return RequestDismissUserAdmin.fromBytes(payload); case 2789: return RequestTransferOwnership.fromBytes(payload); diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/RequestJoinGroupByPeer.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/RequestJoinGroupByPeer.java new file mode 100644 index 0000000000..261437e7b3 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/RequestJoinGroupByPeer.java @@ -0,0 +1,65 @@ +package im.actor.core.api.rpc; +/* + * Generated by the Actor API Scheme generator. DO NOT EDIT! + */ + +import im.actor.runtime.bser.*; +import im.actor.runtime.collections.*; +import static im.actor.runtime.bser.Utils.*; +import im.actor.core.network.parser.*; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.NotNull; +import com.google.j2objc.annotations.ObjectiveCName; +import java.io.IOException; +import java.util.List; +import java.util.ArrayList; +import im.actor.core.api.*; + +public class RequestJoinGroupByPeer extends Request { + + public static final int HEADER = 0xaa2; + public static RequestJoinGroupByPeer fromBytes(byte[] data) throws IOException { + return Bser.parse(new RequestJoinGroupByPeer(), data); + } + + private ApiGroupOutPeer groupPeer; + + public RequestJoinGroupByPeer(@NotNull ApiGroupOutPeer groupPeer) { + this.groupPeer = groupPeer; + } + + public RequestJoinGroupByPeer() { + + } + + @NotNull + public ApiGroupOutPeer getGroupPeer() { + return this.groupPeer; + } + + @Override + public void parse(BserValues values) throws IOException { + this.groupPeer = values.getObj(1, new ApiGroupOutPeer()); + } + + @Override + public void serialize(BserWriter writer) throws IOException { + if (this.groupPeer == null) { + throw new IOException(); + } + writer.writeObject(1, this.groupPeer); + } + + @Override + public String toString() { + String res = "rpc JoinGroupByPeer{"; + res += "groupPeer=" + this.groupPeer; + res += "}"; + return res; + } + + @Override + public int getHeaderKey() { + return HEADER; + } +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateChatGroupsChanged.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateChatGroupsChanged.java index 715757376f..2e88bbef18 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateChatGroupsChanged.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateChatGroupsChanged.java @@ -54,7 +54,7 @@ public void serialize(BserWriter writer) throws IOException { @Override public String toString() { String res = "update ChatGroupsChanged{"; - res += "dialogs=" + this.dialogs; + res += "dialogs=" + this.dialogs.size(); res += "}"; return res; } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsModule.java index c1c2fd83b5..0b15da2b12 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsModule.java @@ -25,6 +25,7 @@ import im.actor.core.api.rpc.RequestGetIntegrationToken; import im.actor.core.api.rpc.RequestInviteUser; import im.actor.core.api.rpc.RequestJoinGroup; +import im.actor.core.api.rpc.RequestJoinGroupByPeer; import im.actor.core.api.rpc.RequestKickUser; import im.actor.core.api.rpc.RequestLeaveAndDelete; import im.actor.core.api.rpc.RequestLeaveGroup; @@ -343,6 +344,15 @@ public Promise joinGroupByToken(final String token) { .map(responseJoinGroup -> responseJoinGroup.getGroup().getId()); } + public Promise joinGroup(int gid) { + return getGroups().getValueAsync(gid) + .flatMap(group -> + api(new RequestJoinGroupByPeer( + new ApiGroupOutPeer(group.getGroupId(), group.getAccessHash())))) + .chain(r -> updates().waitForUpdate(r.getSeq())) + .map(r -> null); + } + // // Integration Token // diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/history/ConversationHistoryActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/history/ConversationHistoryActor.java index 4cceb5cdea..6ac9f1609d 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/history/ConversationHistoryActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/history/ConversationHistoryActor.java @@ -21,6 +21,7 @@ import im.actor.core.modules.api.ApiSupportConfiguration; import im.actor.core.modules.ModuleContext; import im.actor.core.modules.ModuleActor; +import im.actor.runtime.Log; import im.actor.runtime.actors.ask.AskMessage; import im.actor.runtime.actors.messages.Void; import im.actor.runtime.function.Consumer; @@ -69,27 +70,38 @@ private void onLoadMore() { api(new RequestLoadHistory(buidOutPeer(peer), historyMaxDate, null, LIMIT, ApiSupportConfiguration.OPTIMIZATIONS)) .chain(r -> updates().applyRelatedData(r.getUsers(), r.getGroups())) .chain(r -> updates().loadRequiredPeers(r.getUserPeers(), r.getGroupPeers())) - .then(applyHistory(peer)) - .then(responseLoadHistory -> { + .flatMap(r -> { + Log.d("HistoryActor", "Apply " + historyMaxDate); + return applyHistory(peer, r.getHistory()); + }) + .map(r -> { + Log.d("HistoryActor", "Applied"); isFreezed = false; unstashAll(); + return null; }); } - private void onReset() { - historyMaxDate = 0; + private Promise onReset() { + + Log.d("HistoryActor", "Reset"); + + historyMaxDate = Long.MAX_VALUE; preferences().putLong(KEY_LOADED_DATE, Long.MAX_VALUE); historyLoaded = false; preferences().putBool(KEY_LOADED, false); preferences().putBool(KEY_LOADED_INIT, false); - self().send(new LoadMore()); - } - private Consumer applyHistory(final Peer peer) { - return responseLoadHistory -> applyHistory(peer, responseLoadHistory.getHistory()); + isFreezed = true; + return context().getMessagesModule().getRouter().onChatReset(peer) + .then(r -> { + isFreezed = false; + unstashAll(); + onLoadMore(); + }); } - private void applyHistory(Peer peer, List history) { + private Promise applyHistory(Peer peer, List history) { ArrayList messages = new ArrayList<>(); long maxLoadedDate = Long.MAX_VALUE; @@ -120,9 +132,9 @@ private void applyHistory(Peer peer, List history) { // Sending updates to conversation actor final long finalMaxLoadedDate = maxLoadedDate; - context().getMessagesModule().getRouter() + return context().getMessagesModule().getRouter() .onChatHistoryLoaded(peer, messages, maxReceiveDate, maxReadDate, isEnded) - .then(r -> { + .map(r -> { // Saving Internal State if (isEnded) { historyLoaded = true; @@ -133,6 +145,7 @@ private void applyHistory(Peer peer, List history) { preferences().putLong(KEY_LOADED_DATE, finalMaxLoadedDate); preferences().putBool(KEY_LOADED, historyLoaded); preferences().putBool(KEY_LOADED_INIT, true); + return r; }); } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java index fffd5922b0..4b5923c64a 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java @@ -61,11 +61,13 @@ import im.actor.core.modules.messaging.router.entity.RouterOutgoingMessage; import im.actor.core.modules.messaging.router.entity.RouterOutgoingSent; import im.actor.core.modules.messaging.router.entity.RouterPeersChanged; +import im.actor.core.modules.messaging.router.entity.RouterResetChat; import im.actor.core.network.parser.Update; import im.actor.core.util.JavaUtil; import im.actor.core.viewmodel.DialogGroup; import im.actor.core.viewmodel.DialogSmall; import im.actor.core.viewmodel.generics.ArrayListDialogSmall; +import im.actor.runtime.Log; import im.actor.runtime.actors.messages.Void; import im.actor.runtime.promise.Promise; import im.actor.runtime.storage.KeyValueEngine; @@ -402,6 +404,8 @@ private Promise onDialogHistoryLoaded(List dialogs) { private Promise onChatHistoryLoaded(Peer peer, List messages, Long maxReadDate, Long maxReceiveDate, boolean isEnded) { + Log.d(TAG, "History Loaded"); + long maxMessageDate = 0; // Processing all new messages @@ -521,18 +525,22 @@ private Promise onChatClear(Peer peer) { } private Promise onChatDropCache(Peer peer) { - return context().getMessagesModule().getHistoryActor(peer).reset() - .flatMap(r -> { - conversation(peer).clear(); + return context().getMessagesModule().getHistoryActor(peer).reset(); + } - ConversationState state = conversationStates.getValue(peer.getUnuqueId()); - state = state.changeIsLoaded(false); - conversationStates.addOrUpdateItem(state); + private Promise onChatReset(Peer peer) { - updateChatState(peer); + Log.d(TAG, "onChatReset"); - return getDialogsRouter().onChatClear(peer); - }); + conversation(peer).clear(); + + ConversationState state = conversationStates.getValue(peer.getUnuqueId()); + state = state.changeIsLoaded(false); + conversationStates.addOrUpdateItem(state); + + updateChatState(peer); + + return Promise.success(null); } private Promise onChatDelete(Peer peer) { @@ -981,6 +989,9 @@ public Promise onAsk(Object message) throws Exception { return onMessageDeleted( routerDeletedMessages.getPeer(), routerDeletedMessages.getRids()); + } else if (message instanceof RouterResetChat) { + RouterResetChat resetChat = (RouterResetChat) message; + return onChatReset(resetChat.getPeer()); } else { return super.onAsk(message); } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterInt.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterInt.java index df8370adc0..1bc228aba6 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterInt.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterInt.java @@ -28,6 +28,7 @@ import im.actor.core.modules.messaging.router.entity.RouterOutgoingError; import im.actor.core.modules.messaging.router.entity.RouterOutgoingMessage; import im.actor.core.modules.messaging.router.entity.RouterPeersChanged; +import im.actor.core.modules.messaging.router.entity.RouterResetChat; import im.actor.core.network.parser.Update; import im.actor.runtime.actors.ActorInterface; import im.actor.runtime.actors.messages.Void; @@ -150,6 +151,10 @@ public Promise onPeersChanged(List users, List groups) { return ask(new RouterPeersChanged(users, groups)); } + // Resetting + public Promise onChatReset(Peer peer) { + return ask(new RouterResetChat(peer)); + } @Override public void onBusEvent(Event event) { diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/entity/RouterResetChat.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/entity/RouterResetChat.java new file mode 100644 index 0000000000..cc00cec447 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/entity/RouterResetChat.java @@ -0,0 +1,18 @@ +package im.actor.core.modules.messaging.router.entity; + +import im.actor.core.entity.Peer; +import im.actor.runtime.actors.ask.AskMessage; +import im.actor.runtime.actors.messages.Void; + +public class RouterResetChat implements AskMessage { + + private Peer peer; + + public RouterResetChat(Peer peer) { + this.peer = peer; + } + + public Peer getPeer() { + return peer; + } +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/sequence/SequenceHandlerActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/sequence/SequenceHandlerActor.java index 21da5d001c..654a7b1c87 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/sequence/SequenceHandlerActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/sequence/SequenceHandlerActor.java @@ -78,7 +78,10 @@ private Promise onSeqUpdate(final Update update, // Update Application currentPromise = currentPromise - .chain(v -> processor.processUpdate(update)); + .chain(v -> processor.processUpdate(update)) + .then(v -> { + Log.d(TAG, "Ended processing update: " + update); + }); // Handling update end currentPromise.then(v -> endUpdates()); From d5a83051d5909622ca3de0c7a766906f2422cd53 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Mon, 25 Jul 2016 18:04:06 +0300 Subject: [PATCH 118/414] feat(iOS): Removed obsolete string --- .../ActorSDK/Resources/Base.lproj/Localizable.strings | 2 -- .../ActorSDK/Resources/es.lproj/Localizable.strings | 2 -- .../ActorSDK/Resources/pt.lproj/Localizable.strings | 2 -- .../ActorSDK/Resources/ru.lproj/Localizable.strings | 2 -- .../ActorSDK/Resources/zh-Hans.lproj/Localizable.strings | 2 -- .../Sources/Controllers/Group/AAGroupViewController.swift | 2 +- 6 files changed, 1 insertion(+), 11 deletions(-) diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/Base.lproj/Localizable.strings b/actor-sdk/sdk-core-ios/ActorSDK/Resources/Base.lproj/Localizable.strings index 80c8361c52..e54b96b6de 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Resources/Base.lproj/Localizable.strings +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/Base.lproj/Localizable.strings @@ -288,8 +288,6 @@ "GroupIntegrations" = "Integrations"; -"GroupMembers" = "{0} MEMBERS"; - "GroupViewMembers" = "Members"; "GroupDescription" = "Description"; diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/es.lproj/Localizable.strings b/actor-sdk/sdk-core-ios/ActorSDK/Resources/es.lproj/Localizable.strings index d7249c346a..a2e00f084a 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Resources/es.lproj/Localizable.strings +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/es.lproj/Localizable.strings @@ -285,8 +285,6 @@ "GroupIntegrations" = "Integraciones"; -"GroupMembers" = "{0} MIEMBROS"; - "GroupViewMembers" = "Miembros"; "GroupDescription" = "Description"; diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/pt.lproj/Localizable.strings b/actor-sdk/sdk-core-ios/ActorSDK/Resources/pt.lproj/Localizable.strings index 06cb435186..e867ef01ff 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Resources/pt.lproj/Localizable.strings +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/pt.lproj/Localizable.strings @@ -276,8 +276,6 @@ "GroupIntegrations" = "Integrações"; -"GroupMembers" = "{0} MEMBROS"; - "GroupDescription" = "Description"; diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/ru.lproj/Localizable.strings b/actor-sdk/sdk-core-ios/ActorSDK/Resources/ru.lproj/Localizable.strings index df6bd18933..176a1b2015 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Resources/ru.lproj/Localizable.strings +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/ru.lproj/Localizable.strings @@ -289,8 +289,6 @@ "GroupIntegrations" = "Интеграции"; -"GroupMembers" = "{0} УЧАСТНИКИ"; - "GroupDescription" = "Описание"; diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/zh-Hans.lproj/Localizable.strings b/actor-sdk/sdk-core-ios/ActorSDK/Resources/zh-Hans.lproj/Localizable.strings index 912b4f75c2..f01e50f481 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Resources/zh-Hans.lproj/Localizable.strings +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/zh-Hans.lproj/Localizable.strings @@ -270,8 +270,6 @@ "GroupIntegrations" = "集成"; -"GroupMembers" = "{0}个成员"; - "GroupViewMembers" = "个成员"; "GroupMemberAdmin" = "管理员"; diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewController.swift index 4c1eeecddb..371bc03798 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewController.swift @@ -194,7 +194,7 @@ public class AAGroupViewController: AAContentTableController { s.headerHeight = 0 // Members: Header - s.header(AALocalized("GroupMembers").uppercaseString) + s.header(AALocalized("GroupViewMembers").uppercaseString) // Members: Add s.action("GroupAddParticipant") { (r) -> () in From b287128e8dd2ea122e9dc372c2c0b79d3d3badaa Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Mon, 25 Jul 2016 19:26:31 +0300 Subject: [PATCH 119/414] feat(android): Updated versioning --- actor-sdk/sdk-core-android/android-google-maps/build.gradle | 4 +++- actor-sdk/sdk-core-android/android-google-push/build.gradle | 4 +++- actor-sdk/sdk-core-android/android-sdk/build.gradle | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-google-maps/build.gradle b/actor-sdk/sdk-core-android/android-google-maps/build.gradle index 1c803cff8c..0dd918943d 100644 --- a/actor-sdk/sdk-core-android/android-google-maps/build.gradle +++ b/actor-sdk/sdk-core-android/android-google-maps/build.gradle @@ -18,6 +18,8 @@ apply plugin: 'me.tatarka.retrolambda' group = 'im.actor' version = '0.0.1' +def baseVersion = "2.0" + android { compileSdkVersion 24 buildToolsVersion "24.0.0" @@ -110,7 +112,7 @@ if (project.rootProject.file('gradle.properties').exists()) { properties.load(project.rootProject.file('gradle.properties').newDataInputStream()) ossrhUsername = properties.getProperty("ossrhUsername", "") ossrhPassword = properties.getProperty("ossrhPassword", "") - version = properties.getProperty("version", "") + version = baseVersion + properties.getProperty("build_index", "") nexusStaging { username ossrhUsername diff --git a/actor-sdk/sdk-core-android/android-google-push/build.gradle b/actor-sdk/sdk-core-android/android-google-push/build.gradle index d522ad2d21..e001ae7479 100644 --- a/actor-sdk/sdk-core-android/android-google-push/build.gradle +++ b/actor-sdk/sdk-core-android/android-google-push/build.gradle @@ -16,6 +16,8 @@ apply plugin: 'me.tatarka.retrolambda' group = 'im.actor' version = '0.0.1' +def baseVersion = "2.0" + android { compileSdkVersion 24 buildToolsVersion "24.0.0" @@ -107,7 +109,7 @@ if (project.rootProject.file('gradle.properties').exists()) { properties.load(project.rootProject.file('gradle.properties').newDataInputStream()) ossrhUsername = properties.getProperty("ossrhUsername", "") ossrhPassword = properties.getProperty("ossrhPassword", "") - version = properties.getProperty("version", "") + version = baseVersion + properties.getProperty("build_index", "") nexusStaging { username ossrhUsername diff --git a/actor-sdk/sdk-core-android/android-sdk/build.gradle b/actor-sdk/sdk-core-android/android-sdk/build.gradle index 568d657b1c..474c4ffc41 100644 --- a/actor-sdk/sdk-core-android/android-sdk/build.gradle +++ b/actor-sdk/sdk-core-android/android-sdk/build.gradle @@ -16,6 +16,8 @@ apply plugin: 'me.tatarka.retrolambda' group = 'im.actor' version = '0.0.1' +def baseVersion = "2.0" + android { compileSdkVersion 24 @@ -157,7 +159,7 @@ if (project.rootProject.file('gradle.properties').exists()) { properties.load(project.rootProject.file('gradle.properties').newDataInputStream()) ossrhUsername = properties.getProperty("ossrhUsername", "") ossrhPassword = properties.getProperty("ossrhPassword", "") - version = properties.getProperty("version", "") + version = baseVersion + properties.getProperty("build_index", "") nexusStaging { username ossrhUsername From d54cc34b26709d551017d5b7d18a08546fcfc6cb Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Mon, 25 Jul 2016 19:32:52 +0300 Subject: [PATCH 120/414] feat(android): Updated versioning --- actor-sdk/sdk-core-android/android-google-maps/build.gradle | 2 +- actor-sdk/sdk-core-android/android-google-push/build.gradle | 2 +- actor-sdk/sdk-core-android/android-sdk/build.gradle | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-google-maps/build.gradle b/actor-sdk/sdk-core-android/android-google-maps/build.gradle index 0dd918943d..8b47e517fb 100644 --- a/actor-sdk/sdk-core-android/android-google-maps/build.gradle +++ b/actor-sdk/sdk-core-android/android-google-maps/build.gradle @@ -18,7 +18,7 @@ apply plugin: 'me.tatarka.retrolambda' group = 'im.actor' version = '0.0.1' -def baseVersion = "2.0" +def baseVersion = "3.0" android { compileSdkVersion 24 diff --git a/actor-sdk/sdk-core-android/android-google-push/build.gradle b/actor-sdk/sdk-core-android/android-google-push/build.gradle index e001ae7479..788b88b806 100644 --- a/actor-sdk/sdk-core-android/android-google-push/build.gradle +++ b/actor-sdk/sdk-core-android/android-google-push/build.gradle @@ -16,7 +16,7 @@ apply plugin: 'me.tatarka.retrolambda' group = 'im.actor' version = '0.0.1' -def baseVersion = "2.0" +def baseVersion = "3.0" android { compileSdkVersion 24 diff --git a/actor-sdk/sdk-core-android/android-sdk/build.gradle b/actor-sdk/sdk-core-android/android-sdk/build.gradle index 474c4ffc41..d0dea75cef 100644 --- a/actor-sdk/sdk-core-android/android-sdk/build.gradle +++ b/actor-sdk/sdk-core-android/android-sdk/build.gradle @@ -16,7 +16,7 @@ apply plugin: 'me.tatarka.retrolambda' group = 'im.actor' version = '0.0.1' -def baseVersion = "2.0" +def baseVersion = "3.0" android { From e36eb6872917fed127b9fbdbeb9448387a79d4b0 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Mon, 25 Jul 2016 19:35:08 +0300 Subject: [PATCH 121/414] feat(android): Reporting version to teamcity --- actor-sdk/sdk-core-android/android-sdk/build.gradle | 2 ++ 1 file changed, 2 insertions(+) diff --git a/actor-sdk/sdk-core-android/android-sdk/build.gradle b/actor-sdk/sdk-core-android/android-sdk/build.gradle index d0dea75cef..835f300b91 100644 --- a/actor-sdk/sdk-core-android/android-sdk/build.gradle +++ b/actor-sdk/sdk-core-android/android-sdk/build.gradle @@ -161,6 +161,8 @@ if (project.rootProject.file('gradle.properties').exists()) { ossrhPassword = properties.getProperty("ossrhPassword", "") version = baseVersion + properties.getProperty("build_index", "") + print("##teamcity[buildNumber '$version']") + nexusStaging { username ossrhUsername password ossrhPassword From 06c4d815fb33738c78ad007f188212eb316e94d5 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Mon, 25 Jul 2016 19:39:46 +0300 Subject: [PATCH 122/414] fix(android): Fixing version reporting --- actor-sdk/sdk-core-android/android-google-maps/build.gradle | 2 +- actor-sdk/sdk-core-android/android-google-push/build.gradle | 2 +- actor-sdk/sdk-core-android/android-sdk/build.gradle | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-google-maps/build.gradle b/actor-sdk/sdk-core-android/android-google-maps/build.gradle index 8b47e517fb..e25667ecd2 100644 --- a/actor-sdk/sdk-core-android/android-google-maps/build.gradle +++ b/actor-sdk/sdk-core-android/android-google-maps/build.gradle @@ -112,7 +112,7 @@ if (project.rootProject.file('gradle.properties').exists()) { properties.load(project.rootProject.file('gradle.properties').newDataInputStream()) ossrhUsername = properties.getProperty("ossrhUsername", "") ossrhPassword = properties.getProperty("ossrhPassword", "") - version = baseVersion + properties.getProperty("build_index", "") + version = baseVersion + "." + properties.getProperty("build_index", "") nexusStaging { username ossrhUsername diff --git a/actor-sdk/sdk-core-android/android-google-push/build.gradle b/actor-sdk/sdk-core-android/android-google-push/build.gradle index 788b88b806..7e6e58cf81 100644 --- a/actor-sdk/sdk-core-android/android-google-push/build.gradle +++ b/actor-sdk/sdk-core-android/android-google-push/build.gradle @@ -109,7 +109,7 @@ if (project.rootProject.file('gradle.properties').exists()) { properties.load(project.rootProject.file('gradle.properties').newDataInputStream()) ossrhUsername = properties.getProperty("ossrhUsername", "") ossrhPassword = properties.getProperty("ossrhPassword", "") - version = baseVersion + properties.getProperty("build_index", "") + version = baseVersion + "." + properties.getProperty("build_index", "") nexusStaging { username ossrhUsername diff --git a/actor-sdk/sdk-core-android/android-sdk/build.gradle b/actor-sdk/sdk-core-android/android-sdk/build.gradle index 835f300b91..de80ee6c67 100644 --- a/actor-sdk/sdk-core-android/android-sdk/build.gradle +++ b/actor-sdk/sdk-core-android/android-sdk/build.gradle @@ -159,7 +159,7 @@ if (project.rootProject.file('gradle.properties').exists()) { properties.load(project.rootProject.file('gradle.properties').newDataInputStream()) ossrhUsername = properties.getProperty("ossrhUsername", "") ossrhPassword = properties.getProperty("ossrhPassword", "") - version = baseVersion + properties.getProperty("build_index", "") + version = baseVersion + "." + properties.getProperty("build_index", "") print("##teamcity[buildNumber '$version']") From df61fa3303ef943e2c7744d3489d47d159e93e1c Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Mon, 25 Jul 2016 19:48:56 +0300 Subject: [PATCH 123/414] fix(android): Fixing member loading --- .../actor/sdk/controllers/group/view/MembersAdapter.java | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/view/MembersAdapter.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/view/MembersAdapter.java index 8a19a48f43..e2f9109706 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/view/MembersAdapter.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/view/MembersAdapter.java @@ -99,16 +99,10 @@ private void loadMore() { callback.onLoaded(); } } - rawMembers.clear(); - rawMembers.addAll(groupMembersSlice.getUids()); nextMembers = groupMembersSlice.getNext(); loaddedToEnd = nextMembers == null; - ArrayList nMembers = new ArrayList<>(); - for (Integer uid : rawMembers) { - nMembers.add(new GroupMember(uid, 0, 0, false)); - } loadInProgress = false; - setMembers(nMembers, false); + setMembers(groupMembersSlice.getMembers(), false); }); } } From b7479fe6817b7f76eb191b68c90af4fab269bf78 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Mon, 25 Jul 2016 20:01:17 +0300 Subject: [PATCH 124/414] feat(scheme): Added showJoinLeaveMessages --- actor-sdk/sdk-api/actor.json | 11 +++++++++++ .../im.actor.api/models/im/actor/api/scheme.mps | 10 ++++++++++ 2 files changed, 21 insertions(+) diff --git a/actor-sdk/sdk-api/actor.json b/actor-sdk/sdk-api/actor.json index 7316b4f0d6..b27bd3cb55 100644 --- a/actor-sdk/sdk-api/actor.json +++ b/actor-sdk/sdk-api/actor.json @@ -10415,6 +10415,12 @@ "argument": "canAdminsEditGroupInfo", "category": "full", "description": " Can admins edit group info" + }, + { + "type": "reference", + "argument": "showJoinLeaveMessages", + "category": "full", + "description": " Should join and leave messages be visible to members" } ], "expandable": "true", @@ -10438,6 +10444,11 @@ "type": "bool", "id": 4, "name": "canAdminsEditGroupInfo" + }, + { + "type": "bool", + "id": 5, + "name": "showJoinLeaveMessages" } ] } diff --git a/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps b/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps index 39bb9626af..dc46e3760b 100644 --- a/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps +++ b/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps @@ -9131,6 +9131,11 @@ + + + + + @@ -9154,6 +9159,11 @@ + + + + + From a50bbd57380cdb744d4157b034c2f3ea1c08487d Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Mon, 25 Jul 2016 20:07:36 +0300 Subject: [PATCH 125/414] feat(iOS): Adding version --- actor-sdk/sdk-core-ios/VERSION | 1 + 1 file changed, 1 insertion(+) create mode 100644 actor-sdk/sdk-core-ios/VERSION diff --git a/actor-sdk/sdk-core-ios/VERSION b/actor-sdk/sdk-core-ios/VERSION new file mode 100644 index 0000000000..415b19fc36 --- /dev/null +++ b/actor-sdk/sdk-core-ios/VERSION @@ -0,0 +1 @@ +2.0 \ No newline at end of file From 88d88dc6c5f038e26f5998a1ca7230419e5f25ad Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Mon, 25 Jul 2016 21:09:05 +0300 Subject: [PATCH 126/414] fix(js): Fixing JS compilation errors --- .../main/java/im/actor/core/js/JsFacade.java | 152 ++++++------------ .../actor/core/js/modules/JsFilesModule.java | 6 +- .../actor/core/js/modules/JsIdleModule.java | 6 +- 3 files changed, 50 insertions(+), 114 deletions(-) diff --git a/actor-sdk/sdk-core/core/core-js/src/main/java/im/actor/core/js/JsFacade.java b/actor-sdk/sdk-core/core/core-js/src/main/java/im/actor/core/js/JsFacade.java index d51189299f..b10a810aa4 100644 --- a/actor-sdk/sdk-core/core/core-js/src/main/java/im/actor/core/js/JsFacade.java +++ b/actor-sdk/sdk-core/core/core-js/src/main/java/im/actor/core/js/JsFacade.java @@ -448,17 +448,9 @@ public JsPromise editMessage(JsPeer peer, String id, String newText) { return JsPromise.create(new JsPromiseExecutor() { @Override public void execute() { - messenger.updateMessage(peer.convert(), newText, Long.parseLong(id)).start(new CommandCallback() { - @Override - public void onResult(Void res) { - resolve(); - } - - @Override - public void onError(Exception e) { - reject(e.getMessage()); - } - }); + messenger.updateMessage(peer.convert(), newText, Long.parseLong(id)) + .then(r -> resolve()) + .failure(e -> reject(e.getMessage())); } }); } @@ -1233,39 +1225,39 @@ private JsArray convertSearchRes(List>() { - @Override - public void onResult(List res) { - Log.d(TAG, "findGroups:result"); - JsArray jsRes = JsArray.createArray().cast(); - for (PeerSearchEntity s : res) { - if (s.getPeer().getPeerType() == PeerType.GROUP) { - jsRes.push(JsPeerSearchResult.create(messenger.buildPeerInfo(s.getPeer()), - s.getDescription(), s.getMembersCount(), (int) (s.getDate() / 1000L), - messenger.buildPeerInfo(Peer.user(s.getCreatorUid())), s.isPublic(), - s.isJoined())); - } else if (s.getPeer().getPeerType() == PeerType.PRIVATE) { - jsRes.push(JsPeerSearchResult.create(messenger.buildPeerInfo(s.getPeer()))); - } - // jsRes.push(); - } - resolve(jsRes); - } - - @Override - public void onError(Exception e) { - Log.d(TAG, "findGroups:error"); - reject(e.getMessage()); - } - }); - } - }); - } +// @UsedByApp +// public JsPromise findGroups() { +// return JsPromise.create(new JsPromiseExecutor() { +// @Override +// public void execute() { +// messenger.findPeers(PeerSearchType.GROUPS).start(new CommandCallback>() { +// @Override +// public void onResult(List res) { +// Log.d(TAG, "findGroups:result"); +// JsArray jsRes = JsArray.createArray().cast(); +// for (PeerSearchEntity s : res) { +// if (s.getPeer().getPeerType() == PeerType.GROUP) { +// jsRes.push(JsPeerSearchResult.create(messenger.buildPeerInfo(s.getPeer()), +// s.getDescription(), s.getMembersCount(), (int) (s.getDate() / 1000L), +// messenger.buildPeerInfo(Peer.user(s.getCreatorUid())), s.isPublic(), +// s.isJoined())); +// } else if (s.getPeer().getPeerType() == PeerType.PRIVATE) { +// jsRes.push(JsPeerSearchResult.create(messenger.buildPeerInfo(s.getPeer()))); +// } +// // jsRes.push(); +// } +// resolve(jsRes); +// } +// +// @Override +// public void onError(Exception e) { +// Log.d(TAG, "findGroups:error"); +// reject(e.getMessage()); +// } +// }); +// } +// }); +// } @UsedByApp public void changeMyAvatar(final JsFile file) { @@ -1329,43 +1321,19 @@ public JsPromise editGroupTitle(final int gid, final String newTitle) { return JsPromise.create(new JsPromiseExecutor() { @Override public void execute() { - //noinspection ConstantConditions - messenger.editGroupTitle(gid, newTitle).start(new CommandCallback() { - @Override - public void onResult(Void res) { - Log.d(TAG, "editGroupTitle:result"); - resolve(); - } - @Override - public void onError(Exception e) { - Log.d(TAG, "editGroupTitle:error"); - reject(e.getMessage()); - } - }); + messenger.editGroupTitle(gid, newTitle) + .then(r -> resolve()) + .failure(e -> reject(e.getMessage())); } + }); } + @UsedByApp public JsPromise editGroupAbout(final int gid, final String newAbout) { - return JsPromise.create(new JsPromiseExecutor() { - @Override - public void execute() { - messenger.editGroupAbout(gid, newAbout).start(new CommandCallback() { - @Override - public void onResult(Void res) { - resolve(); - } - - @Override - public void onError(Exception e) { - Log.e(TAG, e); - reject(e.getMessage()); - } - }); - } - }); + return JsPromise.from(messenger.editGroupAbout(gid, newAbout).map(r -> null)); } @UsedByApp @@ -1386,19 +1354,9 @@ public JsPromise createGroup(final String title, final JsFile file, final int[] public void execute() { String avatarDescriptor = file != null ? provider.registerUploadFile(file) : null; //noinspection ConstantConditions - messenger.createGroup(title, avatarDescriptor, uids).start(new CommandCallback() { - @Override - public void onResult(Integer res) { - Log.d(TAG, "createGroup:result"); - resolve(JsPeer.create(Peer.group(res))); - } - - @Override - public void onError(Exception e) { - Log.d(TAG, "createGroup:error"); - reject(e.getMessage()); - } - }); + messenger.createGroup(title, avatarDescriptor, uids) + .then(r -> resolve(JsPeer.create(Peer.group(r)))) + .failure(e -> reject(e.getMessage())); } }); } @@ -1451,25 +1409,7 @@ public void onError(Exception e) { @UsedByApp public JsPromise leaveGroup(final int gid) { - return JsPromise.create(new JsPromiseExecutor() { - @Override - public void execute() { - //noinspection ConstantConditions - messenger.leaveGroup(gid).start(new CommandCallback() { - @Override - public void onResult(Void res) { - Log.d(TAG, "leaveGroup:result"); - resolve(); - } - - @Override - public void onError(Exception e) { - Log.d(TAG, "leaveGroup:error"); - reject(e.getMessage()); - } - }); - } - }); + return JsPromise.from(messenger.leaveGroup(gid).map(r -> null)); } @UsedByApp diff --git a/actor-sdk/sdk-core/core/core-js/src/main/java/im/actor/core/js/modules/JsFilesModule.java b/actor-sdk/sdk-core/core/core-js/src/main/java/im/actor/core/js/modules/JsFilesModule.java index 65221f229b..82dd212b84 100644 --- a/actor-sdk/sdk-core/core/core-js/src/main/java/im/actor/core/js/modules/JsFilesModule.java +++ b/actor-sdk/sdk-core/core/core-js/src/main/java/im/actor/core/js/modules/JsFilesModule.java @@ -22,10 +22,8 @@ import im.actor.core.network.RpcException; import im.actor.runtime.Log; import im.actor.runtime.Storage; -import im.actor.runtime.actors.ActorCreator; +import im.actor.runtime.actors.ActorCancellable; import im.actor.runtime.actors.ActorRef; -import im.actor.runtime.actors.Cancellable; -import im.actor.runtime.actors.Props; import static im.actor.runtime.actors.ActorSystem.system; @@ -137,7 +135,7 @@ private static class FileBinderActor extends ModuleActor { private boolean isLoading = false; private JsFilesModule filesModule; private ArrayList filesQueue = new ArrayList<>(); - private Cancellable performCancellable; + private ActorCancellable performCancellable; public FileBinderActor(JsFilesModule filesModule, ModuleContext context) { super(context); diff --git a/actor-sdk/sdk-core/core/core-js/src/main/java/im/actor/core/js/modules/JsIdleModule.java b/actor-sdk/sdk-core/core/core-js/src/main/java/im/actor/core/js/modules/JsIdleModule.java index a4137ea121..0be53dc8fc 100644 --- a/actor-sdk/sdk-core/core/core-js/src/main/java/im/actor/core/js/modules/JsIdleModule.java +++ b/actor-sdk/sdk-core/core/core-js/src/main/java/im/actor/core/js/modules/JsIdleModule.java @@ -5,10 +5,8 @@ import im.actor.core.modules.AbsModule; import im.actor.core.modules.ModuleActor; import im.actor.core.modules.ModuleContext; -import im.actor.runtime.actors.ActorCreator; +import im.actor.runtime.actors.ActorCancellable; import im.actor.runtime.actors.ActorRef; -import im.actor.runtime.actors.Cancellable; -import im.actor.runtime.actors.Props; import static im.actor.runtime.actors.ActorSystem.system; @@ -41,7 +39,7 @@ private static class IdleActor extends ModuleActor implements JsIdleCallback { private boolean isAppVisible = true; private JsMessenger messenger; - private Cancellable flushCancellable; + private ActorCancellable flushCancellable; private boolean isElectron = JsElectronApp.isElectron(); public IdleActor(JsMessenger messenger, ModuleContext context) { From ba6de5932bc82fd92b1d8bb78890052c3b2477da Mon Sep 17 00:00:00 2001 From: rockjam Date: Thu, 14 Jul 2016 05:32:40 +0300 Subject: [PATCH 127/414] wip(server): channels logic implementation --- .../actor-core/src/main/protobuf/dialog.proto | 4 +- .../src/main/protobuf/groupV2.proto | 16 ++- .../server/dialog/DialogCommandHandlers.scala | 10 +- .../actor/server/dialog/DialogExtension.scala | 4 +- .../server/group/GroupCommandHandlers.scala | 70 ++++++++--- .../actor/server/group/GroupOperations.scala | 19 ++- .../group/GroupPeerCommandHandlers.scala | 38 ++++-- .../actor/server/group/GroupProcessor.scala | 7 ++ .../server/group/GroupQueryHandlers.scala | 115 +++++++++++++----- .../im/actor/server/group/GroupState.scala | 13 +- .../messaging/MessagingServiceImpl.scala | 1 + 11 files changed, 223 insertions(+), 74 deletions(-) diff --git a/actor-server/actor-core/src/main/protobuf/dialog.proto b/actor-server/actor-core/src/main/protobuf/dialog.proto index 0910ff52ba..bd5016f130 100644 --- a/actor-server/actor-core/src/main/protobuf/dialog.proto +++ b/actor-server/actor-core/src/main/protobuf/dialog.proto @@ -259,7 +259,9 @@ message DialogCommands { google.protobuf.StringValue delivery_tag = 13; } - message SendMessageAck {} + message SendMessageAck { + Peer updated_sender = 1; + } message MessageReceived { option (scalapb.message).extends = "im.actor.server.dialog.DirectDialogCommand"; diff --git a/actor-server/actor-core/src/main/protobuf/groupV2.proto b/actor-server/actor-core/src/main/protobuf/groupV2.proto index f00627bb63..405a836fa3 100644 --- a/actor-server/actor-core/src/main/protobuf/groupV2.proto +++ b/actor-server/actor-core/src/main/protobuf/groupV2.proto @@ -39,13 +39,14 @@ message GroupEnvelope { GroupQueries.GetAccessHash get_access_hash = 15; GroupQueries.GetTitle get_title = 16; GroupQueries.GetIntegrationToken get_integration_token = 17; - GroupQueries.GetMembers get_members = 18; + GroupQueries.GetMembers get_members = 18; // internalGetMembers GroupQueries.LoadMembers load_members = 19; GroupQueries.IsPublic is_public = 20; GroupQueries.IsHistoryShared is_history_shared = 21; GroupQueries.GetApiStruct get_api_struct = 22; GroupQueries.GetApiFullStruct get_api_full_struct = 23; GroupQueries.CheckAccessHash check_access_hash = 24; + GroupQueries.CanSendMessage can_send_message = 26; } DialogEnvelope dialog_envelope = 25; } @@ -273,4 +274,17 @@ message GroupQueries { repeated int32 user_ids = 1; google.protobuf.BytesValue offset = 2;// should it be Option[Array[Byte]] } + + message CanSendMessage { + option (scalapb.message).extends = "GroupQuery"; + + int32 client_user_id = 1; + } + + message CanSendMessageResponse { + bool can_send = 1; + bool is_channel = 2; + repeated int32 member_ids = 3; + google.protobuf.Int32Value bot_id = 4; + } } diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogCommandHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogCommandHandlers.scala index 478c984b2a..548cd039ba 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogCommandHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogCommandHandlers.scala @@ -58,15 +58,19 @@ trait DialogCommandHandlers extends PeersImplicits with UserAcl { case false ⇒ withNonBlockedPeer[SeqStateDate](userId, sm.getDest)( default = for { - _ ← dialogExt.ackSendMessage(peer, sm.copy(date = Some(sendDate))) - _ ← db.run(writeHistoryMessage(selfPeer, peer, new DateTime(sendDate), sm.randomId, message.header, message.toByteArray)) + SendMessageAck(updatedSender) ← dialogExt.ackSendMessage(peer, sm.withDate(sendDate)) + finalPeer = updatedSender getOrElse selfPeer + _ ← db.run(writeHistoryMessage(finalPeer, peer, new DateTime(sendDate), sm.randomId, message.header, message.toByteArray)) //_ = dialogExt.updateCounters(peer, userId) + // not sure about sender user id. It could be wrong when we have updatedSender! SeqState(seq, state) ← deliveryExt.senderDelivery(userId, optClientAuthId, peer, sm.randomId, sendDate, message, sm.isFat, sm.deliveryTag) } yield SeqStateDate(seq, state, sendDate), failed = for { _ ← db.run(writeHistoryMessageSelf(userId, peer, userId, new DateTime(sendDate), sm.randomId, message.header, message.toByteArray)) SeqState(seq, state) ← deliveryExt.senderDelivery(userId, optClientAuthId, peer, sm.randomId, sendDate, message, sm.isFat, sm.deliveryTag) - } yield SeqStateDate(seq, state, sendDate) + } yield { + SeqStateDate(seq, state, sendDate) + } ) } } yield seqStateDate diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogExtension.scala b/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogExtension.scala index f90749ab58..69a2611377 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogExtension.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogExtension.scala @@ -152,8 +152,8 @@ final class DialogExtensionImpl(system: ActorSystem) extends DialogExtension wit } yield seqStateDate.copy(seq = seq, state = state) } - def ackSendMessage(peer: Peer, sm: SendMessage): Future[Unit] = - (processorRegion(peer) ? envelope(peer, DialogEnvelope().withSendMessage(sm))).mapTo[SendMessageAck] map (_ ⇒ ()) + def ackSendMessage(peer: Peer, sm: SendMessage): Future[SendMessageAck] = + (processorRegion(peer) ? envelope(peer, DialogEnvelope().withSendMessage(sm))).mapTo[SendMessageAck] def writeMessage( peer: ApiPeer, diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala index 9fa5cb7a14..e66f708253 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala @@ -121,7 +121,7 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { ) /////////////////////////// - // new group api updates // + // Groups V2 API updates // /////////////////////////// // send service message to group @@ -176,15 +176,15 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { val dateMillis = evt.ts.toEpochMilli val memberIds = newState.memberIds - val members = newState.members.values.map(_.asStruct).toVector + val apiMembers = newState.members.values.map(_.asStruct).toVector // if user ever been in this group - we should push these updates, // but don't push them if user is first time in group. in this case we should push FatSeqUpdate - val inviteeUpdatesNew: List[Update] = refreshGroupUpdates(newState) + val inviteeUpdatesNew: List[Update] = refreshGroupUpdates(newState, cmd.inviteeUserId) // send everyone in group, including invitee. // send `FatSeqUpdate` if this user invited to group for first time. - val membersUpdateNew = UpdateGroupMembersUpdated(groupId, members) + val membersUpdateNew = UpdateGroupMembersUpdated(groupId, apiMembers) val inviteeUpdateObsolete = UpdateGroupInviteObsolete( groupId, @@ -205,6 +205,37 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { //TODO: remove deprecated db.run(GroupUserRepo.create(groupId, cmd.inviteeUserId, cmd.inviterUserId, evt.ts, None, isAdmin = false)) + // def inviteGROUPUpdates: Future[SeqState] = + // for { + // _ ← seqUpdExt.deliverUserUpdate( + // userId = cmd.inviteeUserId, + // membersUpdateNew, + // pushRules = seqUpdExt.pushRules(isFat = !inviteeIsExUser, Some(PushTexts.Invited)), + // deliveryId = s"invite_${groupId}_${cmd.randomId}" + // ) + // + // // push all group updates to inviteeUserId + // _ ← FutureExt.ftraverse(inviteeUpdatesNew) { update ⇒ + // seqUpdExt.deliverUserUpdate(userId = cmd.inviteeUserId, update) + // } + // + // // push updated members list to all group members except inviteeUserId + // seqState ← seqUpdExt.broadcastClientUpdate( + // userId = cmd.inviterUserId, + // authId = cmd.inviterAuthId, + // bcastUserIds = (memberIds - cmd.inviterUserId) - cmd.inviteeUserId, + // update = membersUpdateNew, + // deliveryId = s"useradded_${groupId}_${cmd.randomId}" + // ) + // /////////////////////////////////////////////// + // } yield seqState + + // def inviteCHANNELUpdates: Future[SeqState] = if(newState.isAdmin()) + // for { + // + // + // } yield () + val result: Future[SeqStateDate] = for { /////////////////////////// // old group api updates // @@ -236,9 +267,10 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { ) /////////////////////////// - // new group api updates // + // Groups V2 API updates // /////////////////////////// + ////////////////////////////////////////////////// // push updated members list to inviteeUserId, _ ← seqUpdExt.deliverUserUpdate( userId = cmd.inviteeUserId, @@ -260,6 +292,7 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { update = membersUpdateNew, deliveryId = s"useradded_${groupId}_${cmd.randomId}" ) + ////////////////////////////////////////////////// // explicitly send service message SeqStateDate(_, _, date) ← dialogExt.sendServerMessage( @@ -309,7 +342,7 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { // If user was invited to group by other member - we don't need to push group updates, // cause they we pushed already on invite step val joiningUserUpdatesNew: List[Update] = - if (wasInvited) List.empty[Update] else refreshGroupUpdates(newState) + if (wasInvited) List.empty[Update] else refreshGroupUpdates(newState, cmd.joiningUserId) // нужно отправить ему апдейт о членах в группе. // всем нужно отправить апдейт о изменившихся членах в группе. можно в любом случае отправить @@ -348,7 +381,7 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { ) /////////////////////////// - // new group api updates // + // Groups V2 API updates // /////////////////////////// // push all group updates to joiningUserId @@ -440,7 +473,7 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { ) /////////////////////////// - // new group api updates // + // Groups V2 API updates // /////////////////////////// // push updated members list to all group members @@ -526,7 +559,7 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { ) /////////////////////////// - // new group api updates // + // Groups V2 API updates // /////////////////////////// // push updated members list to all group members. Don't push to kicked user! @@ -561,6 +594,7 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { } } + //TODO: channels, don't allow non-admin to change topic protected def updateAvatar(cmd: UpdateAvatar): Unit = { if (state.nonMember(cmd.clientUserId)) { sender() ! notMember @@ -585,7 +619,7 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { _ ← seqUpdExt.broadcastClientUpdate(cmd.clientUserId, cmd.clientAuthId, memberIds, updateObsolete) /////////////////////////// - // new group api updates // + // Groups V2 API updates // /////////////////////////// SeqState(seq, state) ← seqUpdExt.broadcastClientUpdate( @@ -609,6 +643,7 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { } } + //TODO: channels, don't allow non-admin to change topic protected def updateTitle(cmd: UpdateTitle): Unit = { val title = cmd.title if (state.nonMember(cmd.clientUserId)) { @@ -651,7 +686,7 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { ) /////////////////////////// - // new group api updates // + // Groups V2 API updates // /////////////////////////// SeqState(seq, state) ← seqUpdExt.broadcastClientUpdate( @@ -677,6 +712,7 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { } } + //TODO: channels, don't allow non-admin to change topic protected def updateTopic(cmd: UpdateTopic): Unit = { def isValidTopic(topic: Option[String]) = topic.forall(_.length < 255) @@ -722,7 +758,7 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { ) /////////////////////////// - // new group api updates // + // Groups V2 API updates // /////////////////////////// SeqState(seq, state) ← seqUpdExt.broadcastClientUpdate( @@ -786,7 +822,7 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { ) /////////////////////////// - // new group api updates // + // Groups V2 API updates // /////////////////////////// SeqState(seq, state) ← seqUpdExt.broadcastClientUpdate( @@ -872,7 +908,7 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { ) /////////////////////////// - // new group api updates // + // Groups V2 API updates // /////////////////////////// _ ← seqUpdExt.broadcastPeopleUpdate( @@ -928,15 +964,15 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { // Updates that will be sent to user, when he enters group. // Helps clients that have this group to refresh it's data. // TODO: review when chanels will be added - private def refreshGroupUpdates(newState: GroupState): List[Update] = List( + private def refreshGroupUpdates(newState: GroupState, userId: Int): List[Update] = List( UpdateGroupMemberChanged(groupId, isMember = true), UpdateGroupAboutChanged(groupId, newState.about), UpdateGroupAvatarChanged(groupId, newState.avatar), UpdateGroupTopicChanged(groupId, newState.topic), UpdateGroupTitleChanged(groupId, newState.title), UpdateGroupOwnerChanged(groupId, newState.ownerUserId), - UpdateGroupCanViewMembersChanged(groupId, canViewMembers = true), - UpdateGroupCanInviteMembersChanged(groupId, canInviteMembers = true) + UpdateGroupCanViewMembersChanged(groupId, canViewMembers = newState.canViewMembers(userId)), + UpdateGroupCanInviteMembersChanged(groupId, canInviteMembers = true) // TODO: figure out right value // UpdateGroupExtChanged(groupId, newState.extension) //TODO: figure out and fix // if(bigGroup) UpdateGroupMembersCountChanged(groupId, newState.extension) ) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupOperations.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupOperations.scala index d350a2657d..b599ef87ce 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupOperations.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupOperations.scala @@ -150,11 +150,21 @@ private[group] sealed trait Queries { .withCheckAccessHash(CheckAccessHash(hash))).mapTo[CheckAccessHashResponse] map (_.isCorrect) //(memberIds, invitedUserIds, botId) - def getMemberIds(groupId: Int): Future[(Seq[Int], Seq[Int], Option[Int])] = + // TODO: should be signed as internal API, and become narrowly scoped + def getMemberIds(groupId: Int): Future[(Seq[Int], Seq[Int], Option[Int])] = //TODO: prepare for channel (viewRegion.ref ? GroupEnvelope(groupId) .withGetMembers(GetMembers())).mapTo[GetMembersResponse] map (r ⇒ (r.memberIds, r.invitedUserIds, r.botId)) + def canSendMessage(groupId: Int, clientUserId: Int): Future[CanSendMessageInfo] = + (viewRegion.ref ? + GroupEnvelope(groupId) + .withCanSendMessage(CanSendMessage(clientUserId))).mapTo[CanSendMessageResponse] map { + case CanSendMessageResponse(canSend, isChannel, memberIds, botId) ⇒ + CanSendMessageInfo(canSend, isChannel, memberIds.toSet, botId) + } + + //TODO: move to separate Query. def isMember(groupId: Int, userId: Int): Future[Boolean] = getMemberIds(groupId) map (_._1.contains(userId)) @@ -173,3 +183,10 @@ private[group] sealed trait Queries { GroupEnvelope(groupId) .withLoadMembers(LoadMembers(clientUserId, limit, offset map ByteString.copyFrom))).mapTo[LoadMembersResponse] map (r ⇒ r.userIds → r.offset.map(_.toByteArray)) } + +final case class CanSendMessageInfo( + canSend: Boolean, + isChannel: Boolean, + memberIds: Set[Int], + botId: Option[Int] +) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupPeerCommandHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupPeerCommandHandlers.scala index 3523f87e28..bca7533ee3 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupPeerCommandHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupPeerCommandHandlers.scala @@ -1,11 +1,10 @@ package im.actor.server.group -import akka.actor.Status import akka.http.scaladsl.util.FastFuture import akka.pattern.pipe import im.actor.api.rpc.PeersImplicits import im.actor.server.dialog.DialogCommands._ -import im.actor.server.group.GroupErrors.NotAMember +import im.actor.server.group.GroupErrors.{ NotAMember, NotAdmin } import im.actor.server.model.Peer import scala.concurrent.Future @@ -17,8 +16,18 @@ trait GroupPeerCommandHandlers extends PeersImplicits { protected def incomingMessage(state: GroupPeerState, sm: SendMessage): Unit = { val senderUserId = sm.getOrigin.id - (withMemberIds(groupId) { (memberIds, _, optBot) ⇒ - if (canSend(memberIds, optBot, senderUserId)) { + + val result: Future[SendMessageAck] = groupExt.canSendMessage(groupId, senderUserId) flatMap { + case CanSendMessageInfo(true, isChannel, memberIds, optBotId) ⇒ + val (ack, smUpdated) = if (isChannel) { + optBotId map { botId ⇒ + val botPeer = Peer.privat(botId) + SendMessageAck().withUpdatedSender(botPeer) → sm.withOrigin(botPeer) + } getOrElse (SendMessageAck() → sm) + } else { + SendMessageAck() → sm + } + val receiverIds = sm.forUserId match { case Some(id) if memberIds.contains(id) ⇒ Seq(id) case _ ⇒ memberIds - senderUserId @@ -26,18 +35,23 @@ trait GroupPeerCommandHandlers extends PeersImplicits { for { _ ← Future.traverse(receiverIds) { userId ⇒ - dialogExt.ackSendMessage(Peer.privat(userId), sm) + dialogExt.ackSendMessage(Peer.privat(userId), smUpdated) } } yield { self ! LastSenderIdChanged(senderUserId) - SendMessageAck() + ack } - } else FastFuture.successful(Status.Failure(NotAMember)) - } recover { - case e ⇒ - log.error(e, "Failed to send message") - throw e - }) pipeTo sender() + case CanSendMessageInfo(false, true, _, _) ⇒ + FastFuture.failed(NotAdmin) + case CanSendMessageInfo(false, _, _, _) ⇒ + FastFuture.failed(NotAMember) + } + + result onFailure { + case e: Exception ⇒ log.error(e, "Failed to send message") + } + + result pipeTo sender() } protected def messageReceived(state: GroupPeerState, mr: MessageReceived) = { diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala index fb053f1d04..5d562402f4 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala @@ -45,6 +45,7 @@ object GroupProcessor { 20017 → classOf[GroupCommands.MakeUserAdmin], 20018 → classOf[GroupCommands.RevokeIntegrationToken], 20020 → classOf[GroupCommands.RevokeIntegrationTokenAck], + 20021 → classOf[GroupCommands.TransferOwnership], 21001 → classOf[GroupQueries.GetIntegrationToken], 21002 → classOf[GroupQueries.GetIntegrationTokenResponse], @@ -60,6 +61,11 @@ object GroupProcessor { 21013 → classOf[GroupQueries.GetAccessHashResponse], 21014 → classOf[GroupQueries.IsHistoryShared], 21015 → classOf[GroupQueries.IsHistorySharedResponse], + 21016 → classOf[GroupQueries.GetTitle], + 21017 → classOf[GroupQueries.LoadMembers], + 21018 → classOf[GroupQueries.GetApiFullStruct], + 21019 → classOf[GroupQueries.CanSendMessage], + 21020 → classOf[GroupQueries.CanSendMessageResponse], 22003 → classOf[GroupEvents.UserInvited], 22004 → classOf[GroupEvents.UserJoined], @@ -153,6 +159,7 @@ private[group] final class GroupProcessor case GetApiStruct(clientUserId) ⇒ getApiStruct(clientUserId) case GetApiFullStruct(clientUserId) ⇒ getApiFullStruct(clientUserId) case CheckAccessHash(accessHash) ⇒ checkAccessHash(accessHash) + case CanSendMessage(clientUserId) ⇒ canSendMessage(clientUserId) } def persistenceId: String = GroupProcessor.persistenceIdFor(groupId) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala index 517d00e127..62655a9e1f 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala @@ -7,6 +7,9 @@ import com.google.protobuf.ByteString import com.google.protobuf.wrappers.Int32Value import im.actor.api.rpc.groups.{ ApiGroup, ApiGroupFull, ApiGroupType, ApiMember } import im.actor.server.group.GroupQueries._ +import im.actor.server.group.GroupType.{ Channel, General, Public } + +import scala.concurrent.Future trait GroupQueryHandlers { self: GroupProcessor ⇒ @@ -19,14 +22,16 @@ trait GroupQueryHandlers { protected def getTitle = FastFuture.successful(GetTitleResponse(state.title)) - protected def getIntegrationToken(optUserId: Option[Int]) = { + protected def getIntegrationToken(optUserId: Option[Int]): Future[GetIntegrationTokenResponse] = { val canViewToken = optUserId.forall(state.isAdmin) FastFuture.successful(GetIntegrationTokenResponse( if (canViewToken) state.bot.map(_.token) else None )) } - protected def getMembers = + //TODO: do something with this method. Will this method used in "client" context. + // If not - don't change it. Maybe rename to `getMembersInternal` + protected def getMembers: Future[GetMembersResponse] = FastFuture.successful { GetMembersResponse( memberIds = state.members.keySet.toSeq, @@ -35,22 +40,33 @@ trait GroupQueryHandlers { ) } - protected def loadMembers(clientUserId: Int, limit: Int, offsetBs: Option[ByteString]) = { - implicit val mat = ActorMaterializer() - val offset = offsetBs map (_.toByteArray) map (Int32Value.parseFrom(_).value) getOrElse 0 - - for { - (userIds, nextOffset) ← Source(state.members.keySet) - .mapAsync(1)(userId ⇒ userExt.getName(userId, clientUserId) map (userId → _)) - .runFold(Vector.empty[(Int, String)])(_ :+ _) map { users ⇒ - val tail = users.sortBy(_._2).map(_._1).drop(offset) - val nextOffset = if (tail.length > limit) Some(Int32Value(offset + limit).toByteArray) else None - (tail.take(limit), nextOffset) - } - } yield LoadMembersResponse( - userIds = userIds, - offset = nextOffset map ByteString.copyFrom - ) + //TODO: rewrite to sort by online + name. Won't work like this + // we can subscribe group object to group onlines! When online comes, we reorder key-set. Use that key set as source. + protected def loadMembers(clientUserId: Int, limit: Int, offsetBs: Option[ByteString]): Future[LoadMembersResponse] = { + def load = { + implicit val mat = ActorMaterializer() + val offset = offsetBs map (_.toByteArray) map (Int32Value.parseFrom(_).value) getOrElse 0 + + for { + (userIds, nextOffset) ← Source(state.members.keySet) + .mapAsync(1)(userId ⇒ userExt.getName(userId, clientUserId) map (userId → _)) + .runFold(Vector.empty[(Int, String)])(_ :+ _) map { users ⇒ + val tail = users.sortBy(_._2).map(_._1).drop(offset) + val nextOffset = if (tail.length > limit) Some(Int32Value(offset + limit).toByteArray) else None + (tail.take(limit), nextOffset) + } + } yield LoadMembersResponse( + userIds = userIds, + offset = nextOffset map ByteString.copyFrom + ) + } + + state.typ match { + case General | Public ⇒ load + case Channel ⇒ + if (state.isAdmin(clientUserId)) load + else FastFuture.successful(LoadMembersResponse(Seq.empty, offsetBs)) + } } protected def isPublic = @@ -63,7 +79,7 @@ trait GroupQueryHandlers { //TODO: what if state changes during request? protected def getApiStruct(clientUserId: Int) = { val isMember = state.isMember(clientUserId) - val apiMembers = getApiMembers(state, clientUserId) + val (members, count) = membersAndCount(state, clientUserId) FastFuture.successful { GetApiStructResponse( @@ -74,14 +90,14 @@ trait GroupQueryHandlers { avatar = state.avatar, isMember = Some(isMember), creatorUserId = state.creatorUserId, - members = apiMembers, - createDate = extractCreatedMillis(state), + members = members, + createDate = extractCreatedAtMillis(state), isAdmin = Some(state.isAdmin(clientUserId)), theme = state.topic, about = state.about, isHidden = Some(state.isHidden), ext = None, - membersCount = Some(apiMembers.size), + membersCount = Some(count), groupType = Some(state.typ match { case GroupType.Channel ⇒ ApiGroupType.CHANNEL case GroupType.General | GroupType.Public | GroupType.Unrecognized(_) ⇒ ApiGroupType.GROUP @@ -101,13 +117,13 @@ trait GroupQueryHandlers { theme = state.topic, about = state.about, ownerUserId = state.creatorUserId, - createDate = extractCreatedMillis(state), + createDate = extractCreatedAtMillis(state), ext = None, - canViewMembers = Some(state.canViewMembers(clientUserId)), - canInvitePeople = Some(state.canInvitePeople(clientUserId)), + canViewMembers = Some(state.canViewMembers(clientUserId)), // TODO: revisit + canInvitePeople = Some(state.canInvitePeople(clientUserId)), // TODO: revisit isSharedHistory = Some(state.isHistoryShared), isAsyncMembers = Some(state.members.size > 100), - members = getApiMembers(state, clientUserId) + members = membersAndCount(state, clientUserId)._1 ) ) } @@ -115,15 +131,50 @@ trait GroupQueryHandlers { protected def checkAccessHash(hash: Long) = FastFuture.successful(CheckAccessHashResponse(isCorrect = state.accessHash == hash)) - private def extractCreatedMillis(group: GroupState): Long = + protected def canSendMessage(clientUserId: Int): Future[CanSendMessageResponse] = + FastFuture.successful { + val canSend = state.bot.exists(_.userId == clientUserId) || { + state.typ match { + case General | Public ⇒ state.isMember(clientUserId) + case Channel ⇒ state.isAdmin(clientUserId) + } + } + CanSendMessageResponse( + canSend = canSend, + isChannel = state.typ.isChannel, + memberIds = state.memberIds.toSeq, + botId = state.bot.map(_.userId) + ) + } + + private def extractCreatedAtMillis(group: GroupState): Long = group.createdAt.map(_.toEpochMilli).getOrElse(throw new RuntimeException("No date created provided for group!")) - private def getApiMembers(group: GroupState, clientUserId: Int) = + /** + * Return group members, and number of members. + * If `clientUserId` is not a group member, return empty members list and 0 + * For `General` and `Public` groups return all members and their number. + * For `Channel` return members list only if `clientUserId` is group admin. Otherwise return empty members list and real members count + */ + private def membersAndCount(group: GroupState, clientUserId: Int): (Vector[ApiMember], Int) = { + def apiMembers = group.members.toVector map { + case (_, m) ⇒ + ApiMember(m.userId, m.inviterUserId, m.invitedAt.toEpochMilli, Some(m.isAdmin)) + } + if (state.isMember(clientUserId)) { - group.members.toVector map { - case (_, m) ⇒ - ApiMember(m.userId, m.inviterUserId, m.invitedAt.toEpochMilli, Some(m.isAdmin)) + state.typ match { + case General | Public ⇒ + apiMembers → group.membersCount + case Channel ⇒ + if (state.isAdmin(clientUserId)) + apiMembers → group.membersCount + else + Vector.empty[ApiMember] → group.membersCount } - } else Vector.empty[ApiMember] + } else { + Vector.empty[ApiMember] → 0 + } + } } diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala index 128d3dac95..fbe9d2dd44 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala @@ -58,7 +58,7 @@ private[group] final case class GroupState( about: Option[String], avatar: Option[Avatar], topic: Option[String], - typ: GroupType, + typ: GroupType, // TODO: rename to groupType isHidden: Boolean, isHistoryShared: Boolean, @@ -86,18 +86,21 @@ private[group] final case class GroupState( def isAdmin(userId: Int): Boolean = members.get(userId) exists (_.isAdmin) + // owner will be super-admin in case of channels def isOwner(userId: Int): Boolean = userId == ownerUserId def isExUser(userId: Int): Boolean = exUserIds.contains(userId) + // in case of general/public can view members if user is member + // in case of channel can view members only if clientUserId is admin def canViewMembers(clientUserId: Int) = - (typ.isGeneral || typ.isPublic) && isMember(clientUserId) + ((typ.isGeneral || typ.isPublic) || isAdmin(clientUserId)) && isMember(clientUserId) + /** + * For now, all members can invite other users to group + */ def canInvitePeople(clientUserId: Int) = isMember(clientUserId) - def canViewMembers(group: GroupState, userId: Int) = - (group.typ.isGeneral || group.typ.isPublic) && isMember(userId) - def isNotCreated = createdAt.isEmpty //TODO: Maybe val. immutable anyway def isCreated = createdAt.nonEmpty //TODO: Maybe val. immutable anyway diff --git a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/messaging/MessagingServiceImpl.scala b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/messaging/MessagingServiceImpl.scala index 5933fcec21..2b7eb0722e 100644 --- a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/messaging/MessagingServiceImpl.scala +++ b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/messaging/MessagingServiceImpl.scala @@ -30,6 +30,7 @@ final class MessagingServiceImpl(implicit protected val actorSystem: ActorSystem override def onFailure: PartialFunction[Throwable, RpcError] = { case GroupErrors.NotAMember ⇒ CommonRpcErrors.forbidden("You are not a group member.") + case GroupErrors.NotAdmin ⇒ CommonRpcErrors.forbidden("Only admin can perform this action.") case DialogErrors.MessageToSelf ⇒ CommonRpcErrors.forbidden("Sending messages to self is not allowed.") case InvalidAccessHash ⇒ CommonRpcErrors.InvalidAccessHash case DialogErrors.DialogAlreadyArchived(peer) ⇒ From 0702c4d7114d5222eb9564ca3f79d881c032b7ae Mon Sep 17 00:00:00 2001 From: rockjam Date: Thu, 14 Jul 2016 06:50:09 +0300 Subject: [PATCH 128/414] wip(server): channels progress --- .../server/group/GroupCommandHandlers.scala | 184 +++++++++++------- .../im/actor/server/group/GroupState.scala | 2 + actor-server/project/Build.scala | 3 +- actor-server/project/StartHook.scala | 15 ++ 4 files changed, 129 insertions(+), 75 deletions(-) create mode 100644 actor-server/project/StartHook.scala diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala index e66f708253..85a57344d3 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala @@ -205,36 +205,62 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { //TODO: remove deprecated db.run(GroupUserRepo.create(groupId, cmd.inviteeUserId, cmd.inviterUserId, evt.ts, None, isAdmin = false)) - // def inviteGROUPUpdates: Future[SeqState] = - // for { - // _ ← seqUpdExt.deliverUserUpdate( - // userId = cmd.inviteeUserId, - // membersUpdateNew, - // pushRules = seqUpdExt.pushRules(isFat = !inviteeIsExUser, Some(PushTexts.Invited)), - // deliveryId = s"invite_${groupId}_${cmd.randomId}" - // ) - // - // // push all group updates to inviteeUserId - // _ ← FutureExt.ftraverse(inviteeUpdatesNew) { update ⇒ - // seqUpdExt.deliverUserUpdate(userId = cmd.inviteeUserId, update) - // } - // - // // push updated members list to all group members except inviteeUserId - // seqState ← seqUpdExt.broadcastClientUpdate( - // userId = cmd.inviterUserId, - // authId = cmd.inviterAuthId, - // bcastUserIds = (memberIds - cmd.inviterUserId) - cmd.inviteeUserId, - // update = membersUpdateNew, - // deliveryId = s"useradded_${groupId}_${cmd.randomId}" - // ) - // /////////////////////////////////////////////// - // } yield seqState - - // def inviteCHANNELUpdates: Future[SeqState] = if(newState.isAdmin()) - // for { - // - // - // } yield () + def inviteGROUPUpdates: Future[SeqState] = + for { + // push updated members list to inviteeUserId, + _ ← seqUpdExt.deliverUserUpdate( + userId = cmd.inviteeUserId, + membersUpdateNew, + pushRules = seqUpdExt.pushRules(isFat = !inviteeIsExUser, Some(PushTexts.Invited)), + deliveryId = s"invite_${groupId}_${cmd.randomId}" + ) + + // push all "refresh group" updates to inviteeUserId + _ ← FutureExt.ftraverse(inviteeUpdatesNew) { update ⇒ + seqUpdExt.deliverUserUpdate(userId = cmd.inviteeUserId, update) + } + + // push updated members list to all group members except inviteeUserId + seqState ← seqUpdExt.broadcastClientUpdate( + userId = cmd.inviterUserId, + authId = cmd.inviterAuthId, + bcastUserIds = (memberIds - cmd.inviterUserId) - cmd.inviteeUserId, + update = membersUpdateNew, + deliveryId = s"useradded_${groupId}_${cmd.randomId}" + ) + /////////////////////////////////////////////// + } yield seqState + + def inviteCHANNELUpdates: Future[SeqState] = + for { + // push `UpdateGroupMembersUpdated` to invitee only if he is admin. + // invitee could be admin, if he created this group, and turning back + _ ← if (newState.isAdmin(cmd.inviteeUserId)) { + seqUpdExt.deliverUserUpdate( + userId = cmd.inviteeUserId, + membersUpdateNew, + pushRules = seqUpdExt.pushRules(isFat = !inviteeIsExUser, Some(PushTexts.Invited)), + deliveryId = s"invite_${groupId}_${cmd.randomId}" + ) + } else { + FastFuture.successful(()) + } + + // push all "refresh group" updates to inviteeUserId + _ ← FutureExt.ftraverse(inviteeUpdatesNew) { update ⇒ + seqUpdExt.deliverUserUpdate(userId = cmd.inviteeUserId, update) + } + + // push updated members list to all ADMINS + _ ← seqUpdExt.broadcastPeopleUpdate( + userIds = newState.adminIds, + update = membersUpdateNew, + deliveryId = s"useradded_${groupId}_${cmd.randomId}" + ) + + // get current SeqState for inviter user + seqState ← seqUpdExt.getSeqState(cmd.inviterUserId, cmd.inviterAuthId) + } yield seqState val result: Future[SeqStateDate] = for { /////////////////////////// @@ -270,29 +296,7 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { // Groups V2 API updates // /////////////////////////// - ////////////////////////////////////////////////// - // push updated members list to inviteeUserId, - _ ← seqUpdExt.deliverUserUpdate( - userId = cmd.inviteeUserId, - membersUpdateNew, - pushRules = seqUpdExt.pushRules(isFat = !inviteeIsExUser, Some(PushTexts.Invited)), - deliveryId = s"invite_${groupId}_${cmd.randomId}" - ) - - // push all group updates to inviteeUserId - _ ← FutureExt.ftraverse(inviteeUpdatesNew) { update ⇒ - seqUpdExt.deliverUserUpdate(userId = cmd.inviteeUserId, update) - } - - // push updated members list to all group members except inviteeUserId - SeqState(seq, state) ← seqUpdExt.broadcastClientUpdate( - userId = cmd.inviterUserId, - authId = cmd.inviterAuthId, - bcastUserIds = (memberIds - cmd.inviterUserId) - cmd.inviteeUserId, - update = membersUpdateNew, - deliveryId = s"useradded_${groupId}_${cmd.randomId}" - ) - ////////////////////////////////////////////////// + SeqState(seq, state) ← if (newState.typ.isChannel) inviteCHANNELUpdates else inviteGROUPUpdates // explicitly send service message SeqStateDate(_, _, date) ← dialogExt.sendServerMessage( @@ -344,9 +348,6 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { val joiningUserUpdatesNew: List[Update] = if (wasInvited) List.empty[Update] else refreshGroupUpdates(newState, cmd.joiningUserId) - // нужно отправить ему апдейт о членах в группе. - // всем нужно отправить апдейт о изменившихся членах в группе. можно в любом случае отправить - // push to everyone, including joining user. // if joining user wasn't invited - send update as FatSeqUpdate // update date when member got into group @@ -366,24 +367,8 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { isAdmin = false )) - val result: Future[(SeqStateDate, Vector[Int], Long)] = + def joinGROUPUpdates: Future[SeqState] = for { - /////////////////////////// - // old group api updates // - /////////////////////////// - - // push update about members to all users, except joining user - _ ← seqUpdExt.broadcastPeopleUpdate( - memberIds - cmd.joiningUserId, - membersUpdateObsolete, - pushRules = seqUpdExt.pushRules(isFat = true, None), - deliveryId = s"userjoined_obsolete_${groupId}_${randomId}" - ) - - /////////////////////////// - // Groups V2 API updates // - /////////////////////////// - // push all group updates to joiningUserId _ ← FutureExt.ftraverse(joiningUserUpdatesNew) { update ⇒ seqUpdExt.deliverUserUpdate(userId = cmd.joiningUserId, update) @@ -391,7 +376,7 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { // push updated members list to joining user, // TODO???: isFat = !wasInvited - is it correct? - SeqState(seq, state) ← seqUpdExt.deliverClientUpdate( + seqState ← seqUpdExt.deliverClientUpdate( userId = cmd.joiningUserId, authId = cmd.joiningUserAuthId, update = membersUpdateNew, @@ -406,6 +391,57 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { membersUpdateNew, deliveryId = s"userjoined_${groupId}_${randomId}" ) + } yield seqState + + def joinCHANNELUpdates: Future[SeqState] = + for { + // push all group updates to joiningUserId + _ ← FutureExt.ftraverse(joiningUserUpdatesNew) { update ⇒ + seqUpdExt.deliverUserUpdate(userId = cmd.joiningUserId, update) + } + + // push `UpdateGroupMembersUpdated` to joining user only if he is admin. + // joining user can be admin, if he created this group, and turning back + // TODO???: isFat = !wasInvited - is it correct? + seqState ← if (newState.isAdmin(cmd.joiningUserId)) { + seqUpdExt.deliverClientUpdate( + userId = cmd.joiningUserId, + authId = cmd.joiningUserAuthId, + update = membersUpdateNew, + pushRules = seqUpdExt.pushRules(isFat = !wasInvited, None), //!wasInvited means that user came for first time here + deliveryId = s"join_${groupId}_${randomId}" + ) + } else { + seqUpdExt.getSeqState(cmd.joiningUserId, cmd.joiningUserAuthId) + } + + // push updated members list to all ADMINS + _ ← seqUpdExt.broadcastPeopleUpdate( + newState.adminIds - cmd.joiningUserId, + membersUpdateNew, + deliveryId = s"userjoined_${groupId}_${randomId}" + ) + } yield seqState + + val result: Future[(SeqStateDate, Vector[Int], Long)] = + for { + /////////////////////////// + // old group api updates // + /////////////////////////// + + // push update about members to all users, except joining user + _ ← seqUpdExt.broadcastPeopleUpdate( + memberIds - cmd.joiningUserId, + membersUpdateObsolete, + pushRules = seqUpdExt.pushRules(isFat = true, None), + deliveryId = s"userjoined_obsolete_${groupId}_${randomId}" + ) + + /////////////////////////// + // Groups V2 API updates // + /////////////////////////// + + SeqState(seq, state) ← if (newState.typ.isChannel) joinCHANNELUpdates else joinGROUPUpdates SeqStateDate(_, _, date) ← dialogExt.sendServerMessage( apiGroupPeer, diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala index fbe9d2dd44..ac29c3c85b 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala @@ -74,6 +74,8 @@ private[group] final case class GroupState( def memberIds = members.keySet //TODO: Maybe lazy val. immutable anyway + def adminIds = (members filter (_._2.isAdmin == true)).keySet //TODO: Maybe lazy val. immutable anyway + def membersCount = members.size //TODO: Maybe lazy val. immutable anyway def isMember(userId: Int): Boolean = members.contains(userId) diff --git a/actor-server/project/Build.scala b/actor-server/project/Build.scala index f35f67fcb9..59786e31fb 100644 --- a/actor-server/project/Build.scala +++ b/actor-server/project/Build.scala @@ -9,7 +9,7 @@ import com.typesafe.sbt.SbtMultiJvm.MultiJvmKeys.MultiJvm import com.typesafe.sbt.packager.archetypes.JavaServerAppPackaging import com.typesafe.sbt.packager.debian.JDebPackaging -object Build extends sbt.Build with Versioning with Releasing with Packaging { +object Build extends sbt.Build with Versioning with Releasing with Packaging with StartHook { val ScalaVersion = "2.11.8" val BotKitVersion = getVersion @@ -89,6 +89,7 @@ object Build extends sbt.Build with Versioning with Releasing with Packaging { "actor", file("."), settings = + startUpSettings ++ packagingSettings ++ defaultSettingsServer ++ Revolver.settings ++ diff --git a/actor-server/project/StartHook.scala b/actor-server/project/StartHook.scala new file mode 100644 index 0000000000..62ca90f078 --- /dev/null +++ b/actor-server/project/StartHook.scala @@ -0,0 +1,15 @@ +package im.actor + +import sbt.Keys._ +import sbt._ + +private[actor] trait StartHook { + lazy val startUpSettings = Seq( + onLoad in Global := { state => + + println("============== hello world") + state + } + ) + +} From d94b36af85ff6118d1890bdc15328953068623bf Mon Sep 17 00:00:00 2001 From: rockjam Date: Thu, 14 Jul 2016 07:53:46 +0300 Subject: [PATCH 129/414] wip(server): channel updates --- .../actor/server/dialog/ActorDelivery.scala | 9 +- .../server/group/GroupCommandHandlers.scala | 331 ++++++++++++------ .../server/sequence/SeqUpdatesExtension.scala | 3 + .../operations/DeliveryOperations.scala | 16 +- actor-server/project/Build.scala | 3 +- actor-server/project/StartHook.scala | 15 - 6 files changed, 246 insertions(+), 131 deletions(-) delete mode 100644 actor-server/project/StartHook.scala diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/dialog/ActorDelivery.scala b/actor-server/actor-core/src/main/scala/im/actor/server/dialog/ActorDelivery.scala index e82dbaee32..5433dd0acd 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/dialog/ActorDelivery.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/dialog/ActorDelivery.scala @@ -1,11 +1,9 @@ package im.actor.server.dialog import akka.actor.ActorSystem -import akka.http.scaladsl.util.FastFuture import im.actor.api.rpc.PeersImplicits import im.actor.api.rpc.counters.{ ApiAppCounters, UpdateCountersChanged } import im.actor.api.rpc.messaging._ -import im.actor.server.db.DbExtension import im.actor.server.messaging.PushText import im.actor.server.model.Peer import im.actor.server.sequence.{ PushData, PushRules, SeqState, SeqUpdatesExtension } @@ -54,7 +52,7 @@ final class ActorDelivery()(implicit val system: ActorSystem) .withCensoredText(censoredPushText) .withPeer(peer) ), - deliveryId = deliveryId(peer, randomId), + deliveryId = seqUpdExt.msgDeliveryId(peer, randomId), deliveryTag = deliveryTag ) } yield () @@ -100,7 +98,7 @@ final class ActorDelivery()(implicit val system: ActorSystem) default = Some(senderUpdate), custom = senderAuthId map (authId ⇒ Map(authId → senderClientUpdate)) getOrElse Map.empty, pushRules = PushRules(isFat = isFat, excludeAuthIds = senderAuthId.toSeq), - deliveryId = deliveryId(peer, randomId), + deliveryId = seqUpdExt.msgDeliveryId(peer, randomId), deliveryTag = deliveryTag ) } @@ -134,9 +132,6 @@ final class ActorDelivery()(implicit val system: ActorSystem) ) } yield () - private def deliveryId(peer: Peer, randomId: Long) = - s"msg_${peer.`type`.value}_${peer.id}_${randomId}" - private def reduceKey(prefix: String, peer: Peer): String = s"${prefix}_${peer.`type`.value}_${peer.id}" diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala index 85a57344d3..6d193759ad 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala @@ -8,6 +8,7 @@ import akka.pattern.pipe import im.actor.api.rpc.Update import im.actor.api.rpc.files.ApiAvatar import im.actor.api.rpc.groups._ +import im.actor.api.rpc.messaging.{ ApiMessage, UpdateMessage } import im.actor.api.rpc.users.ApiSex import im.actor.concurrent.FutureExt import im.actor.server.CommonErrors @@ -205,7 +206,7 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { //TODO: remove deprecated db.run(GroupUserRepo.create(groupId, cmd.inviteeUserId, cmd.inviterUserId, evt.ts, None, isAdmin = false)) - def inviteGROUPUpdates: Future[SeqState] = + def inviteGROUPUpdates: Future[SeqStateDate] = for { // push updated members list to inviteeUserId, _ ← seqUpdExt.deliverUserUpdate( @@ -221,17 +222,26 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { } // push updated members list to all group members except inviteeUserId - seqState ← seqUpdExt.broadcastClientUpdate( + SeqState(seq, state) ← seqUpdExt.broadcastClientUpdate( userId = cmd.inviterUserId, authId = cmd.inviterAuthId, bcastUserIds = (memberIds - cmd.inviterUserId) - cmd.inviteeUserId, update = membersUpdateNew, deliveryId = s"useradded_${groupId}_${cmd.randomId}" ) - /////////////////////////////////////////////// - } yield seqState - def inviteCHANNELUpdates: Future[SeqState] = + // explicitly send service message + SeqStateDate(_, _, date) ← dialogExt.sendServerMessage( + apiGroupPeer, + cmd.inviterUserId, + cmd.inviterAuthId, + cmd.randomId, + serviceMessage, + deliveryTag = Some(Optimization.GroupV2) + ) + } yield SeqStateDate(seq, state, date) + + def inviteCHANNELUpdates: Future[SeqStateDate] = for { // push `UpdateGroupMembersUpdated` to invitee only if he is admin. // invitee could be admin, if he created this group, and turning back @@ -258,9 +268,24 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { deliveryId = s"useradded_${groupId}_${cmd.randomId}" ) - // get current SeqState for inviter user - seqState ← seqUpdExt.getSeqState(cmd.inviterUserId, cmd.inviterAuthId) - } yield seqState + // push service message to invitee + _ ← pushUpdateMessage( + userId = cmd.inviteeUserId, + authId = 0L, + ts = dateMillis, + randomId = cmd.randomId, + serviceMessage + ) + + // push service message to inviter and return seqState + SeqState(seq, state) ← pushUpdateMessage( + userId = cmd.inviterUserId, + authId = cmd.inviterAuthId, + ts = dateMillis, + randomId = cmd.randomId, + serviceMessage + ) + } yield SeqStateDate(seq, state, dateMillis) val result: Future[SeqStateDate] = for { /////////////////////////// @@ -296,18 +321,9 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { // Groups V2 API updates // /////////////////////////// - SeqState(seq, state) ← if (newState.typ.isChannel) inviteCHANNELUpdates else inviteGROUPUpdates + seqStateDate ← if (newState.typ.isChannel) inviteCHANNELUpdates else inviteGROUPUpdates - // explicitly send service message - SeqStateDate(_, _, date) ← dialogExt.sendServerMessage( - apiGroupPeer, - cmd.inviterUserId, - cmd.inviterAuthId, - cmd.randomId, - serviceMessage, - deliveryTag = Some(Optimization.GroupV2) - ) - } yield SeqStateDate(seq, state, date) + } yield seqStateDate result pipeTo sender() } @@ -336,6 +352,7 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { val newState = commit(evt) val date = evt.ts + val dateMillis = date.toEpochMilli val memberIds = newState.memberIds val members = newState.members.values.map(_.asStruct).toVector val randomId = ACLUtils.randomLong() @@ -367,7 +384,7 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { isAdmin = false )) - def joinGROUPUpdates: Future[SeqState] = + def joinGROUPUpdates: Future[SeqStateDate] = for { // push all group updates to joiningUserId _ ← FutureExt.ftraverse(joiningUserUpdatesNew) { update ⇒ @@ -376,7 +393,7 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { // push updated members list to joining user, // TODO???: isFat = !wasInvited - is it correct? - seqState ← seqUpdExt.deliverClientUpdate( + SeqState(seq, state) ← seqUpdExt.deliverClientUpdate( userId = cmd.joiningUserId, authId = cmd.joiningUserAuthId, update = membersUpdateNew, @@ -391,9 +408,17 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { membersUpdateNew, deliveryId = s"userjoined_${groupId}_${randomId}" ) - } yield seqState - def joinCHANNELUpdates: Future[SeqState] = + SeqStateDate(_, _, date) ← dialogExt.sendServerMessage( + apiGroupPeer, + senderUserId = cmd.joiningUserId, + senderAuthId = cmd.joiningUserAuthId, + randomId = randomId, + serviceMessage // no delivery tag. This updated handled this way in Groups V1 + ) + } yield SeqStateDate(seq, state, date) + + def joinCHANNELUpdates: Future[SeqStateDate] = for { // push all group updates to joiningUserId _ ← FutureExt.ftraverse(joiningUserUpdatesNew) { update ⇒ @@ -403,7 +428,7 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { // push `UpdateGroupMembersUpdated` to joining user only if he is admin. // joining user can be admin, if he created this group, and turning back // TODO???: isFat = !wasInvited - is it correct? - seqState ← if (newState.isAdmin(cmd.joiningUserId)) { + SeqState(seq, state) ← if (newState.isAdmin(cmd.joiningUserId)) { seqUpdExt.deliverClientUpdate( userId = cmd.joiningUserId, authId = cmd.joiningUserAuthId, @@ -421,7 +446,16 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { membersUpdateNew, deliveryId = s"userjoined_${groupId}_${randomId}" ) - } yield seqState + + // push service message to joining user and return seqState + _ ← pushUpdateMessage( + userId = cmd.joiningUserId, + authId = cmd.joiningUserAuthId, + ts = dateMillis, + randomId = randomId, + serviceMessage + ) + } yield SeqStateDate(seq, state, dateMillis) val result: Future[(SeqStateDate, Vector[Int], Long)] = for { @@ -441,16 +475,9 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { // Groups V2 API updates // /////////////////////////// - SeqState(seq, state) ← if (newState.typ.isChannel) joinCHANNELUpdates else joinGROUPUpdates + seqStateDate ← if (newState.typ.isChannel) joinCHANNELUpdates else joinGROUPUpdates - SeqStateDate(_, _, date) ← dialogExt.sendServerMessage( - apiGroupPeer, - senderUserId = cmd.joiningUserId, - senderAuthId = cmd.joiningUserAuthId, - randomId = randomId, - serviceMessage // no delivery tag. This updated handled this way in Groups V1 - ) - } yield (SeqStateDate(seq, state, date), memberIds.toVector :+ inviterUserId, randomId) + } yield (seqStateDate, memberIds.toVector :+ inviterUserId, randomId) result pipeTo sender() } @@ -474,11 +501,15 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { val updateObsolete = UpdateGroupUserLeaveObsolete(groupId, cmd.userId, dateMillis, cmd.randomId) - val leftUserUpdatesNew = List( - UpdateGroupCanViewMembersChanged(groupId, canViewMembers = false), - UpdateGroupMembersUpdated(groupId, members = Vector.empty), - UpdateGroupCanInviteMembersChanged(groupId, canInviteMembers = false) - ) + val leftUserUpdatesNew = + if (state.typ.isChannel) List( + UpdateGroupCanInviteMembersChanged(groupId, canInviteMembers = false) + ) + else List( + UpdateGroupCanViewMembersChanged(groupId, canViewMembers = false), + UpdateGroupMembersUpdated(groupId, members = Vector.empty), + UpdateGroupCanInviteMembersChanged(groupId, canInviteMembers = false) + ) val membersUpdateNew = UpdateGroupMembersUpdated(groupId, members) @@ -492,6 +523,67 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { } yield () ) + val leaveGROUPUpdates: Future[SeqStateDate] = + for { + // push updated members list to all group members + _ ← seqUpdExt.broadcastPeopleUpdate( + state.memberIds - cmd.userId, + membersUpdateNew + ) + + SeqStateDate(_, _, date) ← dialogExt.sendServerMessage( + apiGroupPeer, + senderUserId = cmd.userId, + senderAuthId = cmd.authId, + randomId = cmd.randomId, + message = serviceMessage, + deliveryTag = Some(Optimization.GroupV2) + ) + + // push left user that he is no longer a member + SeqState(seq, state) ← seqUpdExt.deliverClientUpdate( + userId = cmd.userId, + authId = cmd.authId, + update = UpdateGroupMemberChanged(groupId, isMember = false) + ) + + // push left user updates + // • with empty group members + // • that he can't view and invite members + _ ← FutureExt.ftraverse(leftUserUpdatesNew) { update ⇒ + seqUpdExt.deliverUserUpdate(userId = cmd.userId, update) + } + } yield SeqStateDate(seq, state, date) + + val leaveCHANNELUpdates: Future[SeqStateDate] = + for { + // push updated members list to all ADMINS, except userId(if he was there) + _ ← seqUpdExt.broadcastPeopleUpdate( + state.adminIds - cmd.userId, + membersUpdateNew + ) + + // push service message to left user + _ ← pushUpdateMessage( + userId = cmd.userId, + authId = cmd.authId, + ts = dateMillis, + randomId = cmd.randomId, + message = serviceMessage + ) + + // push left user that he is no longer a member + SeqState(seq, state) ← seqUpdExt.deliverClientUpdate( + userId = cmd.userId, + authId = cmd.authId, + update = UpdateGroupMemberChanged(groupId, isMember = false) + ) + + _ ← FutureExt.ftraverse(leftUserUpdatesNew) { update ⇒ + seqUpdExt.deliverUserUpdate(userId = cmd.userId, update) + } + } yield SeqStateDate(seq, state, dateMillis) + // read this dialog by user that leaves group. don't wait for ack dialogExt.messageRead(apiGroupPeer, cmd.userId, 0L, dateMillis) val result: Future[SeqStateDate] = for { @@ -512,36 +604,9 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { // Groups V2 API updates // /////////////////////////// - // push updated members list to all group members - _ ← seqUpdExt.broadcastPeopleUpdate( - state.memberIds - cmd.userId, - membersUpdateNew - ) + seqStateDate ← if (state.typ.isChannel) leaveCHANNELUpdates else leaveGROUPUpdates - SeqStateDate(_, _, date) ← dialogExt.sendServerMessage( - apiGroupPeer, - senderUserId = cmd.userId, - senderAuthId = cmd.authId, - randomId = cmd.randomId, - message = serviceMessage, - deliveryTag = Some(Optimization.GroupV2) - ) - - // push left user that he is no longer a member - SeqState(seq, state) ← seqUpdExt.deliverClientUpdate( - userId = cmd.userId, - authId = cmd.authId, - update = UpdateGroupMemberChanged(groupId, isMember = false) - ) - - // push left user updates - // • with empty group members - // • that he can't view and invite members - _ ← FutureExt.ftraverse(leftUserUpdatesNew) { update ⇒ - seqUpdExt.deliverUserUpdate(userId = cmd.userId, update) - } - - } yield SeqStateDate(seq, state, date) + } yield seqStateDate result andThen { case _ ⇒ commit(evt) } pipeTo sender() } @@ -549,7 +614,9 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { } protected def kick(cmd: Kick): Unit = { - if (state.nonMember(cmd.kickerUserId) || state.nonMember(cmd.kickedUserId)) { + if (state.typ.isChannel && !state.isAdmin(cmd.kickedUserId)) { + sender() ! notAdmin + } else if (state.nonMember(cmd.kickerUserId) || state.nonMember(cmd.kickedUserId)) { sender() ! notMember } else { persist(UserKicked(Instant.now, cmd.kickedUserId, cmd.kickerUserId)) { evt ⇒ @@ -560,12 +627,17 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { val updateObsolete = UpdateGroupUserKickObsolete(groupId, cmd.kickedUserId, cmd.kickerUserId, dateMillis, cmd.randomId) - val kickedUserUpdatesNew: List[Update] = List( - UpdateGroupCanViewMembersChanged(groupId, canViewMembers = false), - UpdateGroupMembersUpdated(groupId, members = Vector.empty), - UpdateGroupCanInviteMembersChanged(groupId, canInviteMembers = false), - UpdateGroupMemberChanged(groupId, isMember = false) - ) + val kickedUserUpdatesNew: List[Update] = + if (state.typ.isChannel) List( + UpdateGroupCanInviteMembersChanged(groupId, canInviteMembers = false), + UpdateGroupMemberChanged(groupId, isMember = false) + ) + else List( + UpdateGroupCanViewMembersChanged(groupId, canViewMembers = false), + UpdateGroupMembersUpdated(groupId, members = Vector.empty), + UpdateGroupCanInviteMembersChanged(groupId, canInviteMembers = false), + UpdateGroupMemberChanged(groupId, isMember = false) + ) val membersUpdateNew = UpdateGroupMembersUpdated(groupId, members) @@ -579,6 +651,68 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { } yield () ) + val kickGROUPUpdates: Future[SeqStateDate] = + for { + // push updated members list to all group members. Don't push to kicked user! + SeqState(seq, state) ← seqUpdExt.broadcastClientUpdate( + userId = cmd.kickerUserId, + authId = cmd.kickerAuthId, + bcastUserIds = newState.memberIds - cmd.kickerUserId, + update = membersUpdateNew + ) + + SeqStateDate(_, _, date) ← dialogExt.sendServerMessage( + apiGroupPeer, + senderUserId = cmd.kickerUserId, + senderAuthId = cmd.kickerAuthId, + randomId = cmd.randomId, + message = serviceMessage, + deliveryTag = Some(Optimization.GroupV2) + ) + + // push kicked user updates + // • with empty group members + // • that he is no longer a member of group + // • that he can't view and invite members + _ ← FutureExt.ftraverse(kickedUserUpdatesNew) { update ⇒ + seqUpdExt.deliverUserUpdate(userId = cmd.kickedUserId, update) + } + } yield SeqStateDate(seq, state, date) + + val kickCHANNELUpdates: Future[SeqStateDate] = + for { + // push updated members list to all ADMINS. Don't push to kicked user! + SeqState(seq, state) ← seqUpdExt.broadcastClientUpdate( + userId = cmd.kickerUserId, + authId = cmd.kickerAuthId, + bcastUserIds = newState.adminIds - cmd.kickerUserId, + update = membersUpdateNew + ) + + // push service message to kicker user + _ ← pushUpdateMessage( + userId = cmd.kickerUserId, + authId = cmd.kickerAuthId, //??? what's a point? + ts = dateMillis, + randomId = cmd.randomId, + serviceMessage + ) + + // push service message to kicked user + _ ← pushUpdateMessage( + userId = cmd.kickedUserId, + authId = 0L, + ts = dateMillis, + randomId = cmd.randomId, + serviceMessage + ) + + // push kicked user updates + _ ← FutureExt.ftraverse(kickedUserUpdatesNew) { update ⇒ + seqUpdExt.deliverUserUpdate(userId = cmd.kickedUserId, update) + } + } yield SeqStateDate(seq, state, dateMillis) + // read this dialog by kicked user. don't wait for ack dialogExt.messageRead(apiGroupPeer, cmd.kickedUserId, 0L, dateMillis) val result: Future[SeqStateDate] = for { @@ -598,32 +732,9 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { // Groups V2 API updates // /////////////////////////// - // push updated members list to all group members. Don't push to kicked user! - SeqState(seq, state) ← seqUpdExt.broadcastClientUpdate( - userId = cmd.kickerUserId, - authId = cmd.kickerAuthId, - bcastUserIds = newState.memberIds - cmd.kickerUserId, - update = membersUpdateNew - ) - - SeqStateDate(_, _, date) ← dialogExt.sendServerMessage( - apiGroupPeer, - senderUserId = cmd.kickerUserId, - senderAuthId = cmd.kickerAuthId, - randomId = cmd.randomId, - message = serviceMessage, - deliveryTag = Some(Optimization.GroupV2) - ) + seqStateDate ← if (state.typ.isChannel) kickCHANNELUpdates else kickGROUPUpdates - // push kicked user updates - // • with empty group members - // • that he is no longer a member of group - // • that he can't view and invite members - _ ← FutureExt.ftraverse(kickedUserUpdatesNew) { update ⇒ - seqUpdExt.deliverUserUpdate(userId = cmd.kickedUserId, update) - } - - } yield SeqStateDate(seq, state, date) + } yield seqStateDate result pipeTo sender() } @@ -987,6 +1098,26 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { } } + // или все таки будет broadcast? + private def pushUpdateMessage(userId: Int, authId: Long, ts: Long, randomId: Long, message: ApiMessage): Future[SeqState] = { + val messUpdate = UpdateMessage( + peer = apiGroupPeer, + senderUserId = userId, + date = ts, + randomId = randomId, + message = message, + attributes = None, + quotedMessage = None + ) + seqUpdExt.deliverClientUpdate( + userId = userId, + authId = authId, + update = messUpdate, + deliveryId = seqUpdExt.msgDeliveryId(apiGroupPeer.asModel, randomId), + deliveryTag = Some(Optimization.GroupV2) + ) + } + private def trimToEmpty(s: Option[String]): Option[String] = s map (_.trim) filter (_.nonEmpty) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/sequence/SeqUpdatesExtension.scala b/actor-server/actor-core/src/main/scala/im/actor/server/sequence/SeqUpdatesExtension.scala index 9f0b5ede23..ee7837b359 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/sequence/SeqUpdatesExtension.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/sequence/SeqUpdatesExtension.scala @@ -63,6 +63,9 @@ final class SeqUpdatesExtension(_system: ActorSystem) optTs.getOrElse(throw new RuntimeException(s"No Migration timestamp found for ${MigrationNameList.MultiSequence}")) } + def msgDeliveryId(peer: Peer, randomId: Long) = + s"msg_${peer.`type`.value}_${peer.id}_${randomId}" + def getSeqState(userId: Int, authId: Long): Future[SeqState] = (region.ref ? Envelope(userId).withGetSeqState(GetSeqState(authId))).mapTo[SeqState] diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/sequence/operations/DeliveryOperations.scala b/actor-server/actor-core/src/main/scala/im/actor/server/sequence/operations/DeliveryOperations.scala index 62a149c645..80a199b4d0 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/sequence/operations/DeliveryOperations.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/sequence/operations/DeliveryOperations.scala @@ -21,12 +21,13 @@ trait DeliveryOperations { this: SeqUpdatesExtension ⇒ * Send update to all devices of user and return `SeqState` associated with `authId` */ def deliverClientUpdate( - userId: Int, - authId: Long, - update: Update, - pushRules: PushRules = PushRules(), - reduceKey: Option[String] = None, - deliveryId: String = "" + userId: Int, + authId: Long, + update: Update, + pushRules: PushRules = PushRules(), + reduceKey: Option[String] = None, + deliveryId: String = "", + deliveryTag: Option[String] = None ): Future[SeqState] = deliverUpdate( userId, @@ -34,7 +35,8 @@ trait DeliveryOperations { this: SeqUpdatesExtension ⇒ UpdateMapping(default = Some(serializedUpdate(update))), pushRules, reduceKey, - deliveryId + deliveryId, + deliveryTag ) /** diff --git a/actor-server/project/Build.scala b/actor-server/project/Build.scala index 59786e31fb..f35f67fcb9 100644 --- a/actor-server/project/Build.scala +++ b/actor-server/project/Build.scala @@ -9,7 +9,7 @@ import com.typesafe.sbt.SbtMultiJvm.MultiJvmKeys.MultiJvm import com.typesafe.sbt.packager.archetypes.JavaServerAppPackaging import com.typesafe.sbt.packager.debian.JDebPackaging -object Build extends sbt.Build with Versioning with Releasing with Packaging with StartHook { +object Build extends sbt.Build with Versioning with Releasing with Packaging { val ScalaVersion = "2.11.8" val BotKitVersion = getVersion @@ -89,7 +89,6 @@ object Build extends sbt.Build with Versioning with Releasing with Packaging wit "actor", file("."), settings = - startUpSettings ++ packagingSettings ++ defaultSettingsServer ++ Revolver.settings ++ diff --git a/actor-server/project/StartHook.scala b/actor-server/project/StartHook.scala deleted file mode 100644 index 62ca90f078..0000000000 --- a/actor-server/project/StartHook.scala +++ /dev/null @@ -1,15 +0,0 @@ -package im.actor - -import sbt.Keys._ -import sbt._ - -private[actor] trait StartHook { - lazy val startUpSettings = Seq( - onLoad in Global := { state => - - println("============== hello world") - state - } - ) - -} From 55e19e548f93f6923076def2260b460da01cb04e Mon Sep 17 00:00:00 2001 From: rockjam Date: Thu, 14 Jul 2016 08:04:46 +0300 Subject: [PATCH 130/414] wip(server): channel edit only by admin --- .../server/group/GroupCommandHandlers.scala | 59 +++++++++++++------ 1 file changed, 42 insertions(+), 17 deletions(-) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala index 6d193759ad..0a7aa5381f 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala @@ -741,9 +741,10 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { } } - //TODO: channels, don't allow non-admin to change topic protected def updateAvatar(cmd: UpdateAvatar): Unit = { - if (state.nonMember(cmd.clientUserId)) { + if (state.typ.isChannel && !state.isAdmin(cmd.clientUserId)) { + sender() ! notAdmin + } else if (state.nonMember(cmd.clientUserId)) { sender() ! notMember } else { persist(AvatarUpdated(Instant.now, cmd.avatar)) { evt ⇒ @@ -790,10 +791,11 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { } } - //TODO: channels, don't allow non-admin to change topic protected def updateTitle(cmd: UpdateTitle): Unit = { val title = cmd.title - if (state.nonMember(cmd.clientUserId)) { + if (state.typ.isChannel && !state.isAdmin(cmd.clientUserId)) { + sender() ! notAdmin + } else if (state.nonMember(cmd.clientUserId)) { sender() ! notMember } else if (!isValidTitle(title)) { sender() ! Status.Failure(InvalidTitle) @@ -859,13 +861,14 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { } } - //TODO: channels, don't allow non-admin to change topic protected def updateTopic(cmd: UpdateTopic): Unit = { def isValidTopic(topic: Option[String]) = topic.forall(_.length < 255) val topic = trimToEmpty(cmd.topic) - if (state.nonMember(cmd.clientUserId)) { + if (state.typ.isChannel && !state.isAdmin(cmd.clientUserId)) { + sender() ! notAdmin + } else if (state.nonMember(cmd.clientUserId)) { sender() ! notMember } else if (!isValidTopic(topic)) { sender() ! Status.Failure(TopicTooLong) @@ -1041,6 +1044,36 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { //TODO: remove deprecated db.run(GroupUserRepo.makeAdmin(groupId, cmd.candidateUserId)) + val adminGROUPUpdates: Future[SeqStateDate] = + for { + _ ← seqUpdExt.broadcastPeopleUpdate( + userIds = memberIds + cmd.clientUserId, + updateAdmin + ) + SeqState(seq, state) ← seqUpdExt.broadcastClientUpdate( + cmd.clientUserId, + cmd.clientAuthId, + memberIds - cmd.clientUserId, + updateMembers + ) + } yield SeqStateDate(seq, state, dateMillis) + + val adminCHANNELUpdates: Future[SeqStateDate] = + for { + // push admin changed to all + _ ← seqUpdExt.broadcastPeopleUpdate( + userIds = memberIds + cmd.clientUserId, + updateAdmin + ) + // push changed members to admins and fresh admin + SeqState(seq, state) ← seqUpdExt.broadcastClientUpdate( + cmd.clientUserId, + cmd.clientAuthId, + (newState.adminIds - cmd.clientUserId) + cmd.candidateUserId, + updateMembers + ) + } yield SeqStateDate(seq, state, dateMillis) + val result: Future[(Vector[ApiMember], SeqStateDate)] = for { /////////////////////////// @@ -1058,17 +1091,9 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { // Groups V2 API updates // /////////////////////////// - _ ← seqUpdExt.broadcastPeopleUpdate( - userIds = memberIds + cmd.clientUserId, - updateAdmin - ) - SeqState(seq, state) ← seqUpdExt.broadcastClientUpdate( - cmd.clientUserId, - cmd.clientAuthId, - memberIds - cmd.clientUserId, - updateMembers - ) - } yield (members, SeqStateDate(seq, state, dateMillis)) + seqStateDate ← if (state.typ.isChannel) adminCHANNELUpdates else adminGROUPUpdates + + } yield (members, seqStateDate) result pipeTo sender() } From c2aabffbb6788adcf8d495a3ffed967daa9ca71a Mon Sep 17 00:00:00 2001 From: rockjam Date: Thu, 14 Jul 2016 08:22:27 +0300 Subject: [PATCH 131/414] wip(server): push UpdateGroupMembersCountChanged in channel --- .../server/group/GroupCommandHandlers.scala | 24 +++++++++++++++++++ .../server/group/GroupQueryHandlers.scala | 4 ++-- .../im/actor/server/group/GroupState.scala | 2 +- .../api/rpc/service/GroupsServiceSpec.scala | 14 ++--------- 4 files changed, 29 insertions(+), 15 deletions(-) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala index 0a7aa5381f..c3fe238f52 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala @@ -268,6 +268,12 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { deliveryId = s"useradded_${groupId}_${cmd.randomId}" ) + // push UpdateGroupMembersCountChanged to all group members + _ ← seqUpdExt.broadcastPeopleUpdate( + newState.memberIds, + UpdateGroupMembersCountChanged(groupId, newState.membersCount) + ) + // push service message to invitee _ ← pushUpdateMessage( userId = cmd.inviteeUserId, @@ -447,6 +453,12 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { deliveryId = s"userjoined_${groupId}_${randomId}" ) + // push UpdateGroupMembersCountChanged to all group members + _ ← seqUpdExt.broadcastPeopleUpdate( + newState.memberIds, + UpdateGroupMembersCountChanged(groupId, newState.membersCount) + ) + // push service message to joining user and return seqState _ ← pushUpdateMessage( userId = cmd.joiningUserId, @@ -563,6 +575,12 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { membersUpdateNew ) + // push UpdateGroupMembersCountChanged to all group members + _ ← seqUpdExt.broadcastPeopleUpdate( + state.memberIds - cmd.userId, + UpdateGroupMembersCountChanged(groupId, state.membersCount - 1) + ) + // push service message to left user _ ← pushUpdateMessage( userId = cmd.userId, @@ -689,6 +707,12 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { update = membersUpdateNew ) + // push UpdateGroupMembersCountChanged to all group members + _ ← seqUpdExt.broadcastPeopleUpdate( + newState.memberIds, + UpdateGroupMembersCountChanged(groupId, newState.membersCount) + ) + // push service message to kicker user _ ← pushUpdateMessage( userId = cmd.kickerUserId, diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala index 62655a9e1f..4fd40f44c5 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala @@ -119,8 +119,8 @@ trait GroupQueryHandlers { ownerUserId = state.creatorUserId, createDate = extractCreatedAtMillis(state), ext = None, - canViewMembers = Some(state.canViewMembers(clientUserId)), // TODO: revisit - canInvitePeople = Some(state.canInvitePeople(clientUserId)), // TODO: revisit + canViewMembers = Some(state.canViewMembers(clientUserId)), + canInvitePeople = Some(state.canInvitePeople(clientUserId)), isSharedHistory = Some(state.isHistoryShared), isAsyncMembers = Some(state.members.size > 100), members = membersAndCount(state, clientUserId)._1 diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala index ac29c3c85b..d6d4dbf7bd 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala @@ -96,7 +96,7 @@ private[group] final case class GroupState( // in case of general/public can view members if user is member // in case of channel can view members only if clientUserId is admin def canViewMembers(clientUserId: Int) = - ((typ.isGeneral || typ.isPublic) || isAdmin(clientUserId)) && isMember(clientUserId) + isMember(clientUserId) && ((typ.isGeneral || typ.isPublic) || (typ.isChannel && isAdmin(clientUserId))) /** * For now, all members can invite other users to group diff --git a/actor-server/actor-tests/src/test/scala/im/actor/server/api/rpc/service/GroupsServiceSpec.scala b/actor-server/actor-tests/src/test/scala/im/actor/server/api/rpc/service/GroupsServiceSpec.scala index 84b300db8b..5d070d51ea 100644 --- a/actor-server/actor-tests/src/test/scala/im/actor/server/api/rpc/service/GroupsServiceSpec.scala +++ b/actor-server/actor-tests/src/test/scala/im/actor/server/api/rpc/service/GroupsServiceSpec.scala @@ -72,11 +72,11 @@ final class GroupsServiceSpec it should "set 'about' to empty when None comes" in e20 - it should "forbid to set invalid 'about' field (empty, or longer than 255 characters)" in e21 + it should "forbid to set 'about' field longer than 255 characters" in e21 "EditGroupTopic" should "allow any group member to change topic" in e22 - it should "forbid to set invalid topic (empty, or longer than 255 characters)" in e23 + it should "forbid to set topic longer than 255 characters" in e23 it should "set topic to empty when None comes" in e24 @@ -794,11 +794,6 @@ final class GroupsServiceSpec resp shouldEqual Error(GroupRpcErrors.AboutTooLong) } - val emptyAbout = "" - whenReady(service.handleEditGroupAbout(groupOutPeer, 1L, Some(emptyAbout), Vector.empty)) { resp ⇒ - resp shouldEqual Error(GroupRpcErrors.AboutTooLong) - } - val groupAbout = groupExt.getApiFullStruct(groupOutPeer.groupId, user.id).futureValue.about groupAbout shouldEqual None } @@ -845,11 +840,6 @@ final class GroupsServiceSpec resp shouldEqual Error(GroupRpcErrors.TopicTooLong) } - val emptyTopic = "" - whenReady(service.handleEditGroupTopic(groupOutPeer, 2L, Some(emptyTopic), Vector.empty)) { resp ⇒ - resp shouldEqual Error(GroupRpcErrors.TopicTooLong) - } - val groupTopic = groupExt.getApiFullStruct(groupOutPeer.groupId, user.id).futureValue.theme groupTopic shouldEqual None } From 9f0925a1866d5b4eea783b6142f2e56f9ecc065f Mon Sep 17 00:00:00 2001 From: rockjam Date: Thu, 14 Jul 2016 16:33:19 +0300 Subject: [PATCH 132/414] fix(server): set canSendMessage in ApiGroup --- .../scala/im/actor/server/group/GroupQueryHandlers.scala | 2 +- .../main/scala/im/actor/server/group/GroupState.scala | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala index 4fd40f44c5..1500067942 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala @@ -102,7 +102,7 @@ trait GroupQueryHandlers { case GroupType.Channel ⇒ ApiGroupType.CHANNEL case GroupType.General | GroupType.Public | GroupType.Unrecognized(_) ⇒ ApiGroupType.GROUP }), - canSendMessage = None + canSendMessage = Some(state.canSendMessage(clientUserId)) ) ) } diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala index d6d4dbf7bd..5844d86389 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala @@ -7,6 +7,7 @@ import im.actor.api.rpc.misc.ApiExtension import im.actor.server.cqrs.{ Event, ProcessorState } import im.actor.server.file.Avatar import im.actor.server.group.GroupEvents._ +import im.actor.server.group.GroupType.{ Channel, General, Public } private[group] final case class Member( userId: Int, @@ -103,6 +104,14 @@ private[group] final case class GroupState( */ def canInvitePeople(clientUserId: Int) = isMember(clientUserId) + def canSendMessage(clientUserId: Int) = + { + typ match { + case General | Public ⇒ isMember(clientUserId) + case Channel ⇒ isAdmin(clientUserId) + } + } || bot.exists(_.userId == clientUserId) + def isNotCreated = createdAt.isEmpty //TODO: Maybe val. immutable anyway def isCreated = createdAt.nonEmpty //TODO: Maybe val. immutable anyway From d828bf18fe2ac69ce8200dad1903966c144d5e4a Mon Sep 17 00:00:00 2001 From: rockjam Date: Thu, 14 Jul 2016 22:08:38 +0300 Subject: [PATCH 133/414] fix(server): fix service messages in channels --- .../server/group/GroupCommandHandlers.scala | 109 ++++++++---------- .../actor/server/group/GroupOperations.scala | 3 +- .../server/group/GroupQueryHandlers.scala | 2 +- .../im/actor/server/group/GroupState.scala | 6 + 4 files changed, 58 insertions(+), 62 deletions(-) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala index c3fe238f52..9794afaf34 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala @@ -8,7 +8,7 @@ import akka.pattern.pipe import im.actor.api.rpc.Update import im.actor.api.rpc.files.ApiAvatar import im.actor.api.rpc.groups._ -import im.actor.api.rpc.messaging.{ ApiMessage, UpdateMessage } +import im.actor.api.rpc.messaging.{ ApiMessage, ApiServiceMessage, UpdateMessage } import im.actor.api.rpc.users.ApiSex import im.actor.concurrent.FutureExt import im.actor.server.CommonErrors @@ -274,22 +274,17 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { UpdateGroupMembersCountChanged(groupId, newState.membersCount) ) - // push service message to invitee - _ ← pushUpdateMessage( - userId = cmd.inviteeUserId, - authId = 0L, - ts = dateMillis, - randomId = cmd.randomId, - serviceMessage - ) - - // push service message to inviter and return seqState - SeqState(seq, state) ← pushUpdateMessage( + // push service message to invitee and inviter + SeqState(seq, state) ← seqUpdExt.broadcastClientUpdate( userId = cmd.inviterUserId, authId = cmd.inviterAuthId, - ts = dateMillis, - randomId = cmd.randomId, - serviceMessage + Set(cmd.inviteeUserId), + update = serviceMessageUpdate( + cmd.inviterUserId, + dateMillis, + cmd.randomId, + serviceMessage + ) ) } yield SeqStateDate(seq, state, dateMillis) @@ -349,6 +344,11 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { // user was invited in group by other group user val wasInvited = state.isInvited(cmd.joiningUserId) + // trying to figure out who invited joining user. + // Descdending priority: + // • inviter defined in `Join` command (when invited via token) + // • inviter from members list (when invited by other user) + // • group creator (safe fallback) val optMember = state.members.get(cmd.joiningUserId) val inviterUserId = cmd.invitingUserId .orElse(optMember.map(_.inviterUserId)) @@ -459,13 +459,15 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { UpdateGroupMembersCountChanged(groupId, newState.membersCount) ) - // push service message to joining user and return seqState - _ ← pushUpdateMessage( - userId = cmd.joiningUserId, - authId = cmd.joiningUserAuthId, - ts = dateMillis, - randomId = randomId, - serviceMessage + // push service message only to inviter + _ ← seqUpdExt.deliverUserUpdate( + userId = inviterUserId, + update = serviceMessageUpdate( + cmd.joiningUserId, + dateMillis, + randomId, + serviceMessage + ) ) } yield SeqStateDate(seq, state, dateMillis) @@ -581,14 +583,19 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { UpdateGroupMembersCountChanged(groupId, state.membersCount - 1) ) - // push service message to left user - _ ← pushUpdateMessage( - userId = cmd.userId, - authId = cmd.authId, - ts = dateMillis, - randomId = cmd.randomId, - message = serviceMessage - ) + // push service message to user, who invited leaving user + optInviter = state.members.get(cmd.userId) map (_.inviterUserId) + _ ← optInviter map { inviter ⇒ + seqUpdExt.deliverUserUpdate( + userId = cmd.userId, + update = serviceMessageUpdate( + cmd.userId, + dateMillis, + cmd.randomId, + serviceMessage + ) + ) + } getOrElse FastFuture.successful(()) // push left user that he is no longer a member SeqState(seq, state) ← seqUpdExt.deliverClientUpdate( @@ -713,22 +720,15 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { UpdateGroupMembersCountChanged(groupId, newState.membersCount) ) - // push service message to kicker user - _ ← pushUpdateMessage( - userId = cmd.kickerUserId, - authId = cmd.kickerAuthId, //??? what's a point? - ts = dateMillis, - randomId = cmd.randomId, - serviceMessage - ) - - // push service message to kicked user - _ ← pushUpdateMessage( - userId = cmd.kickedUserId, - authId = 0L, - ts = dateMillis, - randomId = cmd.randomId, - serviceMessage + // push service message to kicker and kicked users. + _ ← seqUpdExt.broadcastPeopleUpdate( + userIds = Set(cmd.kickedUserId, cmd.kickerUserId), + update = serviceMessageUpdate( + cmd.kickerUserId, + dateMillis, + cmd.randomId, + serviceMessage + ) ) // push kicked user updates @@ -1147,25 +1147,16 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { } } - // или все таки будет broadcast? - private def pushUpdateMessage(userId: Int, authId: Long, ts: Long, randomId: Long, message: ApiMessage): Future[SeqState] = { - val messUpdate = UpdateMessage( + private def serviceMessageUpdate(senderUserId: Int, date: Long, randomId: Long, message: ApiServiceMessage) = + UpdateMessage( peer = apiGroupPeer, - senderUserId = userId, - date = ts, + senderUserId = senderUserId, + date = date, randomId = randomId, message = message, attributes = None, quotedMessage = None ) - seqUpdExt.deliverClientUpdate( - userId = userId, - authId = authId, - update = messUpdate, - deliveryId = seqUpdExt.msgDeliveryId(apiGroupPeer.asModel, randomId), - deliveryTag = Some(Optimization.GroupV2) - ) - } private def trimToEmpty(s: Option[String]): Option[String] = s map (_.trim) filter (_.nonEmpty) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupOperations.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupOperations.scala index b599ef87ce..adcfb2810a 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupOperations.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupOperations.scala @@ -46,8 +46,7 @@ private[group] sealed trait Commands extends UserAcl { def joinGroup(groupId: Int, joiningUserId: Int, joiningUserAuthId: Long, invitingUserId: Option[Int]): Future[(SeqStateDate, Vector[Int], Long)] = (processorRegion.ref ? GroupEnvelope(groupId) - .withJoin(Join(joiningUserId, joiningUserAuthId, invitingUserId = None)) //None? - ).mapTo[(SeqStateDate, Vector[Int], Long)] + .withJoin(Join(joiningUserId, joiningUserAuthId, invitingUserId = invitingUserId))).mapTo[(SeqStateDate, Vector[Int], Long)] def inviteToGroup(groupId: Int, inviteeUserId: Int, randomId: Long)(implicit client: AuthorizedClientData): Future[SeqStateDate] = inviteToGroup(client.userId, client.authId, groupId, inviteeUserId, randomId) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala index 1500067942..2e390f87b0 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala @@ -122,7 +122,7 @@ trait GroupQueryHandlers { canViewMembers = Some(state.canViewMembers(clientUserId)), canInvitePeople = Some(state.canInvitePeople(clientUserId)), isSharedHistory = Some(state.isHistoryShared), - isAsyncMembers = Some(state.members.size > 100), + isAsyncMembers = Some(state.isAsyncMembers), members = membersAndCount(state, clientUserId)._1 ) ) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala index 5844d86389..77ed12fbbf 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala @@ -116,6 +116,12 @@ private[group] final case class GroupState( def isCreated = createdAt.nonEmpty //TODO: Maybe val. immutable anyway + def isAsyncMembers = + typ match { + case General | Public ⇒ members.size > 100 + case Channel ⇒ true + } + override def updated(e: Event): GroupState = e match { case evt: Created ⇒ this.copy( From ec97ad43991021be45ae3780ea32456a25462f0a Mon Sep 17 00:00:00 2001 From: rockjam Date: Thu, 14 Jul 2016 22:25:28 +0300 Subject: [PATCH 134/414] fix(server): make history shared in channels by default --- .../scala/im/actor/server/group/GroupCommandHandlers.scala | 7 +++++-- .../src/main/scala/im/actor/server/persist/Group.scala | 5 +---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala index 9794afaf34..ab0dc2d387 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala @@ -62,16 +62,19 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { integrationStorage = new IntegrationTokensKeyValueStorage // Group creation + val groupType = GroupType.fromValue(cmd.typ) //FIXME: make it normal enum + val isHistoryShared = groupType.isChannel + persist(Created( ts = createdAt, groupId, - typ = Some(GroupType.fromValue(cmd.typ)), //FIXME: make it normal enum + typ = Some(groupType), creatorUserId = cmd.creatorUserId, accessHash = accessHash, title = cmd.title, userIds = Seq(cmd.creatorUserId), // only creator user becomes group member. all other users are invited via Invite message isHidden = Some(false), - isHistoryShared = Some(false), + isHistoryShared = Some(isHistoryShared), extensions = Seq.empty )) { evt ⇒ val newState = commit(evt) diff --git a/actor-server/actor-persist/src/main/scala/im/actor/server/persist/Group.scala b/actor-server/actor-persist/src/main/scala/im/actor/server/persist/Group.scala index 25eec0a1ff..6d46810bd0 100644 --- a/actor-server/actor-persist/src/main/scala/im/actor/server/persist/Group.scala +++ b/actor-server/actor-persist/src/main/scala/im/actor/server/persist/Group.scala @@ -93,7 +93,7 @@ object GroupRepo { ) } - //??? + @deprecated("Public groups are deprecated in Group V2 API", "2016-06-05") def findPublic = groups.filter(_.isPublic === true).map(_.asGroup).result @@ -122,9 +122,6 @@ object GroupRepo { def updateAbout(id: Int, about: Option[String]) = byIdC.applied(id).map(_.about).update(about) - //??? - def makePublic(id: Int) = byIdC.applied(id).map(_.isPublic).update(true) - @deprecated("Migrations only", "2016-06-05") def makeHidden(id: Int) = byIdC.applied(id).map(_.isHidden).update(true) } From 1d701dd9351b215a168fadaebe2736b4daa7075e Mon Sep 17 00:00:00 2001 From: rockjam Date: Fri, 15 Jul 2016 17:44:15 +0300 Subject: [PATCH 135/414] fix(server): don't write about change service message; push only members count in channels --- .../im/actor/server/cqrs/Processor.scala | 1 + .../server/dialog/DialogCommandHandlers.scala | 1 - .../server/group/GroupCommandHandlers.scala | 190 ++++++++---------- .../im/actor/server/group/GroupState.scala | 12 +- .../im/actor/server/office/PushTexts.scala | 3 +- .../operations/DeliveryOperations.scala | 13 +- 6 files changed, 104 insertions(+), 116 deletions(-) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/cqrs/Processor.scala b/actor-server/actor-core/src/main/scala/im/actor/server/cqrs/Processor.scala index 97ec86eda5..4739346df5 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/cqrs/Processor.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/cqrs/Processor.scala @@ -65,6 +65,7 @@ trait ProcessorStateControl[S <: ProcessorState[S]] { protected def getInitialState: S + //TODO: rename to processorState final def state: S = _state def setState(state: S) = this._state = state diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogCommandHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogCommandHandlers.scala index 548cd039ba..e5c093afa0 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogCommandHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogCommandHandlers.scala @@ -62,7 +62,6 @@ trait DialogCommandHandlers extends PeersImplicits with UserAcl { finalPeer = updatedSender getOrElse selfPeer _ ← db.run(writeHistoryMessage(finalPeer, peer, new DateTime(sendDate), sm.randomId, message.header, message.toByteArray)) //_ = dialogExt.updateCounters(peer, userId) - // not sure about sender user id. It could be wrong when we have updatedSender! SeqState(seq, state) ← deliveryExt.senderDelivery(userId, optClientAuthId, peer, sm.randomId, sendDate, message, sm.isFat, sm.deliveryTag) } yield SeqStateDate(seq, state, sendDate), failed = for { diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala index ab0dc2d387..0c94feacab 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala @@ -8,7 +8,7 @@ import akka.pattern.pipe import im.actor.api.rpc.Update import im.actor.api.rpc.files.ApiAvatar import im.actor.api.rpc.groups._ -import im.actor.api.rpc.messaging.{ ApiMessage, ApiServiceMessage, UpdateMessage } +import im.actor.api.rpc.messaging.{ ApiServiceMessage, UpdateMessage } import im.actor.api.rpc.users.ApiSex import im.actor.concurrent.FutureExt import im.actor.server.CommonErrors @@ -113,7 +113,7 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { val result: Future[CreateAck] = for { /////////////////////////// - // old group api updates // + // Groups V1 API updates // /////////////////////////// _ ← seqUpdExt.deliverUserUpdate( @@ -183,12 +183,13 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { val apiMembers = newState.members.values.map(_.asStruct).toVector // if user ever been in this group - we should push these updates, - // but don't push them if user is first time in group. in this case we should push FatSeqUpdate val inviteeUpdatesNew: List[Update] = refreshGroupUpdates(newState, cmd.inviteeUserId) - // send everyone in group, including invitee. - // send `FatSeqUpdate` if this user invited to group for first time. - val membersUpdateNew = UpdateGroupMembersUpdated(groupId, apiMembers) + val membersUpdateNew: Update = + if (newState.typ.isChannel) // if history shared + UpdateGroupMembersCountChanged(groupId, newState.membersCount) + else + UpdateGroupMembersUpdated(groupId, apiMembers) val inviteeUpdateObsolete = UpdateGroupInviteObsolete( groupId, @@ -212,6 +213,7 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { def inviteGROUPUpdates: Future[SeqStateDate] = for { // push updated members list to inviteeUserId, + // make it `FatSeqUpdate` if this user invited to group for first time. _ ← seqUpdExt.deliverUserUpdate( userId = cmd.inviteeUserId, membersUpdateNew, @@ -246,54 +248,44 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { def inviteCHANNELUpdates: Future[SeqStateDate] = for { - // push `UpdateGroupMembersUpdated` to invitee only if he is admin. - // invitee could be admin, if he created this group, and turning back - _ ← if (newState.isAdmin(cmd.inviteeUserId)) { - seqUpdExt.deliverUserUpdate( - userId = cmd.inviteeUserId, - membersUpdateNew, - pushRules = seqUpdExt.pushRules(isFat = !inviteeIsExUser, Some(PushTexts.Invited)), - deliveryId = s"invite_${groupId}_${cmd.randomId}" - ) - } else { - FastFuture.successful(()) - } + // push updated members count to inviteeUserId + _ ← seqUpdExt.deliverUserUpdate( + userId = cmd.inviteeUserId, + membersUpdateNew, + pushRules = seqUpdExt.pushRules(isFat = false, Some(PushTexts.Invited)), + deliveryId = s"invite_${groupId}_${cmd.randomId}" + ) // push all "refresh group" updates to inviteeUserId _ ← FutureExt.ftraverse(inviteeUpdatesNew) { update ⇒ seqUpdExt.deliverUserUpdate(userId = cmd.inviteeUserId, update) } - // push updated members list to all ADMINS - _ ← seqUpdExt.broadcastPeopleUpdate( - userIds = newState.adminIds, + // push updated members count to all group members + SeqState(seq, state) ← seqUpdExt.broadcastClientUpdate( + userId = cmd.inviterUserId, + authId = cmd.inviterAuthId, + bcastUserIds = (memberIds - cmd.inviterUserId) - cmd.inviteeUserId, update = membersUpdateNew, deliveryId = s"useradded_${groupId}_${cmd.randomId}" ) - // push UpdateGroupMembersCountChanged to all group members - _ ← seqUpdExt.broadcastPeopleUpdate( - newState.memberIds, - UpdateGroupMembersCountChanged(groupId, newState.membersCount) - ) - // push service message to invitee and inviter - SeqState(seq, state) ← seqUpdExt.broadcastClientUpdate( - userId = cmd.inviterUserId, - authId = cmd.inviterAuthId, - Set(cmd.inviteeUserId), + _ ← seqUpdExt.broadcastPeopleUpdate( + userIds = Set(cmd.inviterUserId, cmd.inviteeUserId), update = serviceMessageUpdate( cmd.inviterUserId, dateMillis, cmd.randomId, serviceMessage - ) + ), + deliveryTag = Some(Optimization.GroupV2) ) } yield SeqStateDate(seq, state, dateMillis) val result: Future[SeqStateDate] = for { /////////////////////////// - // old group api updates // + // Groups V1 API updates // /////////////////////////// // push "Invited" to invitee @@ -363,7 +355,7 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { val date = evt.ts val dateMillis = date.toEpochMilli val memberIds = newState.memberIds - val members = newState.members.values.map(_.asStruct).toVector + val apiMembers = newState.members.values.map(_.asStruct).toVector val randomId = ACLUtils.randomLong() // If user was never invited to group - he don't have group on devices, @@ -374,12 +366,13 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { val joiningUserUpdatesNew: List[Update] = if (wasInvited) List.empty[Update] else refreshGroupUpdates(newState, cmd.joiningUserId) - // push to everyone, including joining user. - // if joining user wasn't invited - send update as FatSeqUpdate - // update date when member got into group - val membersUpdateNew = UpdateGroupMembersUpdated(groupId, members) + val membersUpdateNew: Update = + if (newState.typ.isChannel) // if history is shared + UpdateGroupMembersCountChanged(groupId, newState.membersCount) + else + UpdateGroupMembersUpdated(groupId, apiMembers) // will update date when member got into group - val membersUpdateObsolete = UpdateGroupMembersUpdateObsolete(groupId, members) + val membersUpdateObsolete = UpdateGroupMembersUpdateObsolete(groupId, apiMembers) val serviceMessage = GroupServiceMessages.userJoined @@ -401,6 +394,7 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { } // push updated members list to joining user, + // make it `FatSeqUpdate` if this user invited to group for first time. // TODO???: isFat = !wasInvited - is it correct? SeqState(seq, state) ← seqUpdExt.deliverClientUpdate( userId = cmd.joiningUserId, @@ -434,34 +428,21 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { seqUpdExt.deliverUserUpdate(userId = cmd.joiningUserId, update) } - // push `UpdateGroupMembersUpdated` to joining user only if he is admin. - // joining user can be admin, if he created this group, and turning back - // TODO???: isFat = !wasInvited - is it correct? - SeqState(seq, state) ← if (newState.isAdmin(cmd.joiningUserId)) { - seqUpdExt.deliverClientUpdate( - userId = cmd.joiningUserId, - authId = cmd.joiningUserAuthId, - update = membersUpdateNew, - pushRules = seqUpdExt.pushRules(isFat = !wasInvited, None), //!wasInvited means that user came for first time here - deliveryId = s"join_${groupId}_${randomId}" - ) - } else { - seqUpdExt.getSeqState(cmd.joiningUserId, cmd.joiningUserAuthId) - } + // push updated members count to joining user + SeqState(seq, state) ← seqUpdExt.deliverClientUpdate( + userId = cmd.joiningUserId, + authId = cmd.joiningUserAuthId, + update = membersUpdateNew, + deliveryId = s"join_${groupId}_${randomId}" + ) - // push updated members list to all ADMINS + // push updated members count to all group members except joining user _ ← seqUpdExt.broadcastPeopleUpdate( - newState.adminIds - cmd.joiningUserId, + memberIds - cmd.joiningUserId, membersUpdateNew, deliveryId = s"userjoined_${groupId}_${randomId}" ) - // push UpdateGroupMembersCountChanged to all group members - _ ← seqUpdExt.broadcastPeopleUpdate( - newState.memberIds, - UpdateGroupMembersCountChanged(groupId, newState.membersCount) - ) - // push service message only to inviter _ ← seqUpdExt.deliverUserUpdate( userId = inviterUserId, @@ -470,14 +451,15 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { dateMillis, randomId, serviceMessage - ) + ), + deliveryTag = Some(Optimization.GroupV2) ) } yield SeqStateDate(seq, state, dateMillis) val result: Future[(SeqStateDate, Vector[Int], Long)] = for { /////////////////////////// - // old group api updates // + // Groups V1 API updates // /////////////////////////// // push update about members to all users, except joining user @@ -514,7 +496,6 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { // no commit here. it will be after service message sent val dateMillis = evt.ts.toEpochMilli - val members = state.members.filterNot(_._1 == cmd.userId).values.map(_.asStruct).toVector val updateObsolete = UpdateGroupUserLeaveObsolete(groupId, cmd.userId, dateMillis, cmd.randomId) @@ -528,7 +509,18 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { UpdateGroupCanInviteMembersChanged(groupId, canInviteMembers = false) ) - val membersUpdateNew = UpdateGroupMembersUpdated(groupId, members) + val membersUpdateNew = + if (state.typ.isChannel) { // if history is shared + UpdateGroupMembersCountChanged( + groupId, + membersCount = state.membersCount - 1 + ) + } else { + UpdateGroupMembersUpdated( + groupId, + members = state.members.filterNot(_._1 == cmd.userId).values.map(_.asStruct).toVector + ) + } val serviceMessage = GroupServiceMessages.userLeft @@ -548,6 +540,7 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { membersUpdateNew ) + // send service message SeqStateDate(_, _, date) ← dialogExt.sendServerMessage( apiGroupPeer, senderUserId = cmd.userId, @@ -574,16 +567,10 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { val leaveCHANNELUpdates: Future[SeqStateDate] = for { - // push updated members list to all ADMINS, except userId(if he was there) - _ ← seqUpdExt.broadcastPeopleUpdate( - state.adminIds - cmd.userId, - membersUpdateNew - ) - - // push UpdateGroupMembersCountChanged to all group members + // push updated members count to all group members _ ← seqUpdExt.broadcastPeopleUpdate( state.memberIds - cmd.userId, - UpdateGroupMembersCountChanged(groupId, state.membersCount - 1) + membersUpdateNew ) // push service message to user, who invited leaving user @@ -596,7 +583,8 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { dateMillis, cmd.randomId, serviceMessage - ) + ), + deliveryTag = Some(Optimization.GroupV2) ) } getOrElse FastFuture.successful(()) @@ -607,6 +595,7 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { update = UpdateGroupMemberChanged(groupId, isMember = false) ) + // push left user updates that he has no group rights _ ← FutureExt.ftraverse(leftUserUpdatesNew) { update ⇒ seqUpdExt.deliverUserUpdate(userId = cmd.userId, update) } @@ -617,7 +606,7 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { val result: Future[SeqStateDate] = for { /////////////////////////// - // old group api updates // + // Groups V1 API updates // /////////////////////////// _ ← seqUpdExt.broadcastClientUpdate( @@ -651,7 +640,6 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { val newState = commit(evt) val dateMillis = evt.ts.toEpochMilli - val members = newState.members.values.map(_.asStruct).toVector val updateObsolete = UpdateGroupUserKickObsolete(groupId, cmd.kickedUserId, cmd.kickerUserId, dateMillis, cmd.randomId) @@ -667,7 +655,18 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { UpdateGroupMemberChanged(groupId, isMember = false) ) - val membersUpdateNew = UpdateGroupMembersUpdated(groupId, members) + val membersUpdateNew: Update = + if (newState.typ.isChannel) { // if history is shared + UpdateGroupMembersCountChanged( + groupId, + membersCount = newState.membersCount + ) + } else { + UpdateGroupMembersUpdated( + groupId, + members = newState.members.values.map(_.asStruct).toVector + ) + } val serviceMessage = GroupServiceMessages.userKicked(cmd.kickedUserId) @@ -709,20 +708,14 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { val kickCHANNELUpdates: Future[SeqStateDate] = for { - // push updated members list to all ADMINS. Don't push to kicked user! + // push updated members count to all group members. Don't push to kicked user! SeqState(seq, state) ← seqUpdExt.broadcastClientUpdate( userId = cmd.kickerUserId, authId = cmd.kickerAuthId, - bcastUserIds = newState.adminIds - cmd.kickerUserId, + bcastUserIds = newState.memberIds - cmd.kickerUserId, update = membersUpdateNew ) - // push UpdateGroupMembersCountChanged to all group members - _ ← seqUpdExt.broadcastPeopleUpdate( - newState.memberIds, - UpdateGroupMembersCountChanged(groupId, newState.membersCount) - ) - // push service message to kicker and kicked users. _ ← seqUpdExt.broadcastPeopleUpdate( userIds = Set(cmd.kickedUserId, cmd.kickerUserId), @@ -731,10 +724,11 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { dateMillis, cmd.randomId, serviceMessage - ) + ), + deliveryTag = Some(Optimization.GroupV2) ) - // push kicked user updates + // push kicked user updates that he has no group rights _ ← FutureExt.ftraverse(kickedUserUpdatesNew) { update ⇒ seqUpdExt.deliverUserUpdate(userId = cmd.kickedUserId, update) } @@ -744,7 +738,7 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { dialogExt.messageRead(apiGroupPeer, cmd.kickedUserId, 0L, dateMillis) val result: Future[SeqStateDate] = for { /////////////////////////// - // old group api updates // + // Groups V1 API updates // /////////////////////////// _ ← seqUpdExt.broadcastClientUpdate( @@ -788,7 +782,7 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { db.run(AvatarDataRepo.createOrUpdate(getAvatarData(cmd.avatar))) val result: Future[UpdateAvatarAck] = for { /////////////////////////// - // old group api updates // + // Groups V1 API updates // /////////////////////////// _ ← seqUpdExt.broadcastClientUpdate(cmd.clientUserId, cmd.clientAuthId, memberIds, updateObsolete) @@ -850,7 +844,7 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { val result: Future[SeqStateDate] = for { /////////////////////////// - // old group api updates // + // Groups V1 API updates // /////////////////////////// _ ← seqUpdExt.broadcastClientUpdate( @@ -923,7 +917,7 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { val result: Future[SeqStateDate] = for { /////////////////////////// - // old group api updates // + // Groups V1 API updates // /////////////////////////// _ ← seqUpdExt.broadcastClientUpdate( @@ -987,7 +981,7 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { val result: Future[SeqStateDate] = for { /////////////////////////// - // old group api updates // + // Groups V1 API updates // /////////////////////////// _ ← seqUpdExt.broadcastClientUpdate( @@ -1009,15 +1003,7 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { update = updateNew, pushRules = pushRules ) - SeqStateDate(_, _, date) ← dialogExt.sendServerMessage( - apiGroupPeer, - senderUserId = cmd.clientUserId, - senderAuthId = cmd.clientAuthId, - randomId = cmd.randomId, - message = serviceMessage, - deliveryTag = Some(Optimization.GroupV2) - ) - } yield SeqStateDate(seq, state, date) + } yield SeqStateDate(seq, state, evt.ts.toEpochMilli) result pipeTo sender() } @@ -1104,7 +1090,7 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { val result: Future[(Vector[ApiMember], SeqStateDate)] = for { /////////////////////////// - // old group api updates // + // Groups V1 API updates // /////////////////////////// _ ← seqUpdExt.broadcastClientUpdate( diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala index 77ed12fbbf..8251037033 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala @@ -70,14 +70,14 @@ private[group] final case class GroupState( //security and etc. accessHash: Long, bot: Option[Bot], - extensions: Map[Int, Array[Byte]] //or should it be sequence??? + extensions: Map[Int, Array[Byte]] ) extends ProcessorState[GroupState] { - def memberIds = members.keySet //TODO: Maybe lazy val. immutable anyway + lazy val memberIds = members.keySet - def adminIds = (members filter (_._2.isAdmin == true)).keySet //TODO: Maybe lazy val. immutable anyway + lazy val adminIds = (members filter (_._2.isAdmin == true)).keySet - def membersCount = members.size //TODO: Maybe lazy val. immutable anyway + lazy val membersCount = members.size def isMember(userId: Int): Boolean = members.contains(userId) @@ -112,9 +112,9 @@ private[group] final case class GroupState( } } || bot.exists(_.userId == clientUserId) - def isNotCreated = createdAt.isEmpty //TODO: Maybe val. immutable anyway + val isNotCreated = createdAt.isEmpty - def isCreated = createdAt.nonEmpty //TODO: Maybe val. immutable anyway + val isCreated = createdAt.nonEmpty def isAsyncMembers = typ match { diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/office/PushTexts.scala b/actor-server/actor-core/src/main/scala/im/actor/server/office/PushTexts.scala index 95603ffe4e..a270327262 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/office/PushTexts.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/office/PushTexts.scala @@ -1,5 +1,6 @@ package im.actor.server.office +//TODO: make up to date with channels object PushTexts { val Added = "User added" val Invited = "You are invited to a group" @@ -8,4 +9,4 @@ object PushTexts { val TitleChanged = "Group title changed" val TopicChanged = "Group topic changed" val AboutChanged = "Group about changed" -} \ No newline at end of file +} diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/sequence/operations/DeliveryOperations.scala b/actor-server/actor-core/src/main/scala/im/actor/server/sequence/operations/DeliveryOperations.scala index 80a199b4d0..38c7c2140e 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/sequence/operations/DeliveryOperations.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/sequence/operations/DeliveryOperations.scala @@ -95,14 +95,15 @@ trait DeliveryOperations { this: SeqUpdatesExtension ⇒ * Send update to all devices of users from `userIds` set and return `Unit` */ def broadcastPeopleUpdate( - userIds: Set[Int], - update: Update, - pushRules: PushRules = PushRules(), - reduceKey: Option[String] = None, - deliveryId: String = "" + userIds: Set[Int], + update: Update, + pushRules: PushRules = PushRules(), + reduceKey: Option[String] = None, + deliveryId: String = "", + deliveryTag: Option[String] = None ): Future[Unit] = { val mapping = UpdateMapping(default = Some(serializedUpdate(update))) - val deliver = buildDeliver(0L, mapping, pushRules, reduceKey, deliveryId, deliveryTag = None) // TODO: add deliveryTag when needed + val deliver = buildDeliver(0L, mapping, pushRules, reduceKey, deliveryId, deliveryTag) broadcastUpdate(userIds, deliver) } From 77fba0e95dae0aeeba90822a5786a347412ddd19 Mon Sep 17 00:00:00 2001 From: rockjam Date: Fri, 15 Jul 2016 17:46:09 +0300 Subject: [PATCH 136/414] chore(server): updated actor.json --- .../actor-core/src/main/actor-api/actor.json | 560 +++++++++++++++++- 1 file changed, 557 insertions(+), 3 deletions(-) diff --git a/actor-server/actor-core/src/main/actor-api/actor.json b/actor-server/actor-core/src/main/actor-api/actor.json index 4c358dbec1..7219e2b553 100644 --- a/actor-server/actor-core/src/main/actor-api/actor.json +++ b/actor-server/actor-core/src/main/actor-api/actor.json @@ -8140,7 +8140,7 @@ "type": "reference", "argument": "ownerUid", "category": "full", - "description": " Group owner" + "description": " Optional group owner" }, { "type": "reference", @@ -8183,8 +8183,45 @@ "argument": "isSharedHistory", "category": "full", "description": " Is history shared among all users. Default is false." + }, + { + "type": "reference", + "argument": "canEditGroupInfo", + "category": "full", + "description": " If current user can edit group info. Default is true." + }, + { + "type": "reference", + "argument": "shortName", + "category": "full", + "description": " Group's short name" + }, + { + "type": "reference", + "argument": "canEditShortName", + "category": "full", + "description": " If not set only owner can edit group's short name" + }, + { + "type": "reference", + "argument": "canEditAdminList", + "category": "full", + "description": " If not set only owner can edit admin list" + }, + { + "type": "reference", + "argument": "canViewAdminList", + "category": "full", + "description": " If not set only owner and admins can view admin list" + }, + { + "type": "reference", + "argument": "canEditAdminSettings", + "category": "full", + "description": " If not set only owner can edit admin settings" } ], + "expandable": "true", "attributes": [ { "type": { @@ -8204,8 +8241,11 @@ }, { "type": { - "type": "alias", - "childType": "userId" + "type": "opt", + "childType": { + "type": "alias", + "childType": "userId" + } }, "id": 5, "name": "ownerUid" @@ -8279,6 +8319,54 @@ }, "id": 10, "name": "isSharedHistory" + }, + { + "type": { + "type": "opt", + "childType": "bool" + }, + "id": 13, + "name": "canEditGroupInfo" + }, + { + "type": { + "type": "opt", + "childType": "string" + }, + "id": 14, + "name": "shortName" + }, + { + "type": { + "type": "opt", + "childType": "bool" + }, + "id": 15, + "name": "canEditShortName" + }, + { + "type": { + "type": "opt", + "childType": "bool" + }, + "id": 16, + "name": "canEditAdminList" + }, + { + "type": { + "type": "opt", + "childType": "bool" + }, + "id": 17, + "name": "canViewAdminList" + }, + { + "type": { + "type": "opt", + "childType": "bool" + }, + "id": 18, + "name": "canEditAdminSettings" } ] } @@ -8677,6 +8765,46 @@ ] } }, + { + "type": "update", + "content": { + "name": "GroupShortNameChanged", + "header": 2628, + "doc": [ + "Group's short name changed", + { + "type": "reference", + "argument": "groupId", + "category": "full", + "description": " Group Id" + }, + { + "type": "reference", + "argument": "shortName", + "category": "full", + "description": " Group short name" + } + ], + "attributes": [ + { + "type": { + "type": "alias", + "childType": "groupId" + }, + "id": 1, + "name": "groupId" + }, + { + "type": { + "type": "opt", + "childType": "string" + }, + "id": 2, + "name": "shortName" + } + ] + } + }, { "type": "update", "content": { @@ -8854,6 +8982,191 @@ ] } }, + { + "type": "update", + "content": { + "name": "GroupCanEditInfoChanged", + "header": 2631, + "doc": [ + "Update about can edit changed", + { + "type": "reference", + "argument": "groupId", + "category": "full", + "description": " Group Id" + }, + { + "type": "reference", + "argument": "canEditGroup", + "category": "full", + "description": " Can edit group info" + } + ], + "attributes": [ + { + "type": { + "type": "alias", + "childType": "groupId" + }, + "id": 1, + "name": "groupId" + }, + { + "type": "bool", + "id": 2, + "name": "canEditGroup" + } + ] + } + }, + { + "type": "update", + "content": { + "name": "GroupCanEditUsernameChanged", + "header": 2632, + "doc": [ + "Update about can edit username changed", + { + "type": "reference", + "argument": "groupId", + "category": "full", + "description": " Group Id" + }, + { + "type": "reference", + "argument": "canEditUsername", + "category": "full", + "description": " Can edit username" + } + ], + "attributes": [ + { + "type": { + "type": "alias", + "childType": "groupId" + }, + "id": 1, + "name": "groupId" + }, + { + "type": "bool", + "id": 2, + "name": "canEditUsername" + } + ] + } + }, + { + "type": "update", + "content": { + "name": "GroupCanEditAdminsChanged", + "header": 2633, + "doc": [ + "Update about can edit admins changed", + { + "type": "reference", + "argument": "groupId", + "category": "full", + "description": " Group Id" + }, + { + "type": "reference", + "argument": "canAssignAdmins", + "category": "hidden", + "description": " Can assign admins" + } + ], + "attributes": [ + { + "type": { + "type": "alias", + "childType": "groupId" + }, + "id": 1, + "name": "groupId" + }, + { + "type": "bool", + "id": 2, + "name": "canAssignAdmins" + } + ] + } + }, + { + "type": "update", + "content": { + "name": "GroupCanViewAdminsChanged", + "header": 2640, + "doc": [ + "Update about view admings changed", + { + "type": "reference", + "argument": "groupId", + "category": "full", + "description": " Group Id" + }, + { + "type": "reference", + "argument": "canViewAdmins", + "category": "hidden", + "description": " Can view admins" + } + ], + "attributes": [ + { + "type": { + "type": "alias", + "childType": "groupId" + }, + "id": 1, + "name": "groupId" + }, + { + "type": "bool", + "id": 2, + "name": "canViewAdmins" + } + ] + } + }, + { + "type": "update", + "content": { + "name": "GroupCanEditAdminSettingsChanged", + "header": 2641, + "doc": [ + "Update about edit admin settings changed", + { + "type": "reference", + "argument": "groupId", + "category": "full", + "description": " Group Id" + }, + { + "type": "reference", + "argument": "canEditAdminSettings", + "category": "full", + "description": " Can edit admin settings" + } + ], + "attributes": [ + { + "type": { + "type": "alias", + "childType": "groupId" + }, + "id": 1, + "name": "groupId" + }, + { + "type": "bool", + "id": 2, + "name": "canEditAdminSettings" + } + ] + } + }, { "type": "comment", "content": " " @@ -9571,6 +9884,50 @@ ] } }, + { + "type": "rpc", + "content": { + "name": "EditGroupShortName", + "header": 2793, + "response": { + "type": "reference", + "name": "Seq" + }, + "doc": [ + "Edit Group Short Name", + { + "type": "reference", + "argument": "groupPeer", + "category": "full", + "description": "Group's peer" + }, + { + "type": "reference", + "argument": "shortName", + "category": "full", + "description": "New group's short name" + } + ], + "attributes": [ + { + "type": { + "type": "struct", + "childType": "GroupOutPeer" + }, + "id": 1, + "name": "groupPeer" + }, + { + "type": { + "type": "opt", + "childType": "string" + }, + "id": 2, + "name": "shortName" + } + ] + } + }, { "type": "rpc", "content": { @@ -9932,6 +10289,10 @@ ] } }, + { + "type": "comment", + "content": "Administration" + }, { "type": "rpc", "content": { @@ -9976,6 +10337,50 @@ ] } }, + { + "type": "rpc", + "content": { + "name": "DismissUserAdmin", + "header": 2791, + "response": { + "type": "reference", + "name": "Seq" + }, + "doc": [ + "Dismissing user admin", + { + "type": "reference", + "argument": "groupPeer", + "category": "full", + "description": "Group's peer" + }, + { + "type": "reference", + "argument": "userPeer", + "category": "full", + "description": "User's peer" + } + ], + "attributes": [ + { + "type": { + "type": "struct", + "childType": "GroupOutPeer" + }, + "id": 1, + "name": "groupPeer" + }, + { + "type": { + "type": "struct", + "childType": "UserOutPeer" + }, + "id": 2, + "name": "userPeer" + } + ] + } + }, { "type": "rpc", "content": { @@ -10020,6 +10425,155 @@ ] } }, + { + "type": "struct", + "content": { + "name": "AdminSettings", + "doc": [ + "Admin Settings", + { + "type": "reference", + "argument": "showAdminsToMembers", + "category": "full", + "description": " Show admins in member list" + }, + { + "type": "reference", + "argument": "canMembersInvite", + "category": "full", + "description": " Can members of a group invite people" + }, + { + "type": "reference", + "argument": "canMembersEditGroupInfo", + "category": "full", + "description": " Can members edit group info" + }, + { + "type": "reference", + "argument": "canAdminsEditGroupInfo", + "category": "full", + "description": " Can admins edit group info" + } + ], + "expandable": "true", + "attributes": [ + { + "type": "bool", + "id": 1, + "name": "showAdminsToMembers" + }, + { + "type": "bool", + "id": 2, + "name": "canMembersInvite" + }, + { + "type": "bool", + "id": 3, + "name": "canMembersEditGroupInfo" + }, + { + "type": "bool", + "id": 4, + "name": "canAdminsEditGroupInfo" + } + ] + } + }, + { + "type": "rpc", + "content": { + "name": "LoadAdminSettings", + "header": 2790, + "response": { + "type": "anonymous", + "header": 2794, + "doc": [ + "Loaded settings", + { + "type": "reference", + "argument": "settings", + "category": "full", + "description": " Current group admin settings" + } + ], + "attributes": [ + { + "type": { + "type": "struct", + "childType": "AdminSettings" + }, + "id": 1, + "name": "settings" + } + ] + }, + "doc": [ + "Loading administration settings", + { + "type": "reference", + "argument": "groupPeer", + "category": "full", + "description": "Group's peer" + } + ], + "attributes": [ + { + "type": { + "type": "struct", + "childType": "GroupOutPeer" + }, + "id": 1, + "name": "groupPeer" + } + ] + } + }, + { + "type": "rpc", + "content": { + "name": "SaveAdminSettings", + "header": 2792, + "response": { + "type": "reference", + "name": "Void" + }, + "doc": [ + "Save administartion settings", + { + "type": "reference", + "argument": "groupPeer", + "category": "full", + "description": "Group's Peer" + }, + { + "type": "reference", + "argument": "settings", + "category": "full", + "description": "Group's settings" + } + ], + "attributes": [ + { + "type": { + "type": "struct", + "childType": "GroupOutPeer" + }, + "id": 1, + "name": "groupPeer" + }, + { + "type": { + "type": "struct", + "childType": "AdminSettings" + }, + "id": 2, + "name": "settings" + } + ] + } + }, { "type": "comment", "content": "Invite" From 59ad8c3fd564e1d1f316999e236b2c8dd38d7eb7 Mon Sep 17 00:00:00 2001 From: rockjam Date: Mon, 18 Jul 2016 02:49:55 +0300 Subject: [PATCH 137/414] feat(server:groups): set short name for groups --- .../src/main/protobuf/globalname.proto | 20 ++++ .../actor-core/src/main/protobuf/group.proto | 7 ++ .../src/main/protobuf/groupV2.proto | 9 ++ .../scala/im/actor/server/CommonErrors.scala | 2 +- .../server/group/GroupCommandHandlers.scala | 61 +++++++++- .../im/actor/server/group/GroupErrors.scala | 6 + .../actor/server/group/GroupOperations.scala | 8 +- .../actor/server/group/GroupProcessor.scala | 11 +- .../server/group/GroupQueryHandlers.scala | 10 +- .../im/actor/server/group/GroupState.scala | 10 ++ .../server/{office => group}/PushTexts.scala | 2 +- .../server/names/GlobalNamesStorage.scala | 110 ++++++++++++++++++ .../server/user/UserCommandHandlers.scala | 7 +- .../im/actor/server/user/UserProcessor.scala | 2 + .../im/actor/server/persist/UserRepo.scala | 14 +-- .../api/rpc/service/auth/AuthHelpers.scala | 14 +-- .../rpc/service/auth/AuthServiceImpl.scala | 16 +-- .../contacts/ContactsServiceImpl.scala | 6 +- .../rpc/service/groups/GroupRpcErrors.scala | 10 +- .../service/groups/GroupsServiceImpl.scala | 73 +++++++++--- .../service/profile/ProfileServiceImpl.scala | 15 ++- .../im/actor/util/misc/StringUtils.scala | 6 +- .../scala/im/actor/util/StringUtilsSpec.scala | 18 +-- 23 files changed, 362 insertions(+), 75 deletions(-) create mode 100644 actor-server/actor-core/src/main/protobuf/globalname.proto rename actor-server/actor-core/src/main/scala/im/actor/server/{office => group}/PushTexts.scala (91%) create mode 100644 actor-server/actor-core/src/main/scala/im/actor/server/names/GlobalNamesStorage.scala diff --git a/actor-server/actor-core/src/main/protobuf/globalname.proto b/actor-server/actor-core/src/main/protobuf/globalname.proto new file mode 100644 index 0000000000..caadea04eb --- /dev/null +++ b/actor-server/actor-core/src/main/protobuf/globalname.proto @@ -0,0 +1,20 @@ +syntax = "proto3"; + +package im.actor.server.names; + +option (scalapb.options) = { + flat_package: true +}; + + +import "scalapb/scalapb.proto"; + +enum OwnerType { + User = 0; + Group = 1; +} + +message GlobalNameOwner { + OwnerType owner_type = 1; + int32 owner_id = 2; +} diff --git a/actor-server/actor-core/src/main/protobuf/group.proto b/actor-server/actor-core/src/main/protobuf/group.proto index 5092545e6a..b4f50d938c 100644 --- a/actor-server/actor-core/src/main/protobuf/group.proto +++ b/actor-server/actor-core/src/main/protobuf/group.proto @@ -98,6 +98,13 @@ message GroupEvents { optional string description = 1; } + message ShortNameUpdated { + option (scalapb.message).extends = "im.actor.server.group.GroupEvent"; + + required int64 ts = 1 [(scalapb.field).type = "java.time.Instant"]; + optional string short_name = 2; + } + message TopicUpdated { option (scalapb.message).extends = "im.actor.server.group.GroupEvent"; diff --git a/actor-server/actor-core/src/main/protobuf/groupV2.proto b/actor-server/actor-core/src/main/protobuf/groupV2.proto index 405a836fa3..297dd7f9f5 100644 --- a/actor-server/actor-core/src/main/protobuf/groupV2.proto +++ b/actor-server/actor-core/src/main/protobuf/groupV2.proto @@ -30,6 +30,7 @@ message GroupEnvelope { GroupCommands.UpdateTitle update_title = 8; GroupCommands.UpdateTopic update_topic = 9; GroupCommands.UpdateAbout update_about = 10; + GroupCommands.UpdateShortName update_short_name = 27; // GroupCommands.MakePublic make_public = 11; GroupCommands.RevokeIntegrationToken revoke_token = 12; GroupCommands.MakeUserAdmin make_user_admin = 13; @@ -152,6 +153,14 @@ message GroupCommands { int64 random_id = 4; } + message UpdateShortName { + option (scalapb.message).extends = "GroupCommand"; + + int32 client_user_id = 1; + int64 client_auth_id = 2; + google.protobuf.StringValue short_name = 3; + } + message MakeUserAdmin { option (scalapb.message).extends = "GroupCommand"; diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/CommonErrors.scala b/actor-server/actor-core/src/main/scala/im/actor/server/CommonErrors.scala index cd654cbebf..b13311bf09 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/CommonErrors.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/CommonErrors.scala @@ -3,4 +3,4 @@ package im.actor.server object CommonErrors { case class Forbidden(message: String) extends RuntimeException(message) object Forbidden extends Forbidden("You are not allowed to do this.") -} \ No newline at end of file +} diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala index 0c94feacab..16c3ff671e 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala @@ -16,19 +16,20 @@ import im.actor.server.acl.ACLUtils import im.actor.server.dialog.UserAcl import im.actor.server.file.{ Avatar, ImageUtils } import im.actor.server.group.GroupErrors._ -import im.actor.server.group.GroupEvents.{ AboutUpdated, AvatarUpdated, BotAdded, Created, IntegrationTokenRevoked, OwnerChanged, TitleUpdated, TopicUpdated, UserBecameAdmin, UserInvited, UserJoined, UserKicked, UserLeft } +import im.actor.server.group.GroupEvents._ import im.actor.server.group.GroupCommands._ import im.actor.server.model.{ AvatarData, Group } -import im.actor.server.office.PushTexts +import im.actor.server.names.{ GlobalNameOwner, OwnerType } import im.actor.server.persist.{ AvatarDataRepo, GroupBotRepo, GroupInviteTokenRepo, GroupRepo, GroupUserRepo } import im.actor.server.sequence.{ Optimization, SeqState, SeqStateDate } import im.actor.util.ThreadLocalSecureRandom -import im.actor.util.misc.IdUtils +import im.actor.util.misc.{ IdUtils, StringUtils } import scala.concurrent.Future +//TODO: spit into MemberCommandHandlers - InfoCommandHandlers - ControlCommandHandlers private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { - self: GroupProcessor ⇒ + this: GroupProcessor ⇒ import im.actor.server.ApiConversions._ @@ -1010,6 +1011,56 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { } } + protected def updateShortName(cmd: UpdateShortName): Unit = { + def isValidShortName(shortName: Option[String]) = shortName forall StringUtils.validGlobalName + + val oldShortName = state.shortName + val newShortName = trimToEmpty(cmd.shortName) + + if (!state.isOwner(cmd.clientUserId)) { + sender() ! Status.Failure(NotOwner) + } else if (!isValidShortName(newShortName)) { + sender() ! Status.Failure(InvalidShortName) + } else if (oldShortName == newShortName) { + seqUpdExt.getSeqState(cmd.clientUserId, cmd.clientAuthId) pipeTo sender() + } else { + val replyTo = sender() + + val existsFu = newShortName map { name ⇒ + globalNamesStorage.exists(name) + } getOrElse FastFuture.successful(false) + + //TODO: timeout for this + onSuccess(existsFu) { exists ⇒ + if (exists) { + replyTo ! Status.Failure(ShortNameTaken) + } else { + persist(ShortNameUpdated(Instant.now, newShortName)) { evt ⇒ + val newState = commit(evt) + + val memberIds = newState.memberIds + + val result: Future[SeqState] = for { + _ ← globalNamesStorage.updateOrRemove( + oldShortName, + newShortName, + GlobalNameOwner(OwnerType.Group, groupId) + ) + seqState ← seqUpdExt.broadcastClientUpdate( + userId = cmd.clientUserId, + authId = cmd.clientAuthId, + bcastUserIds = memberIds - cmd.clientUserId, + update = UpdateGroupShortNameChanged(groupId, newShortName) + ) + } yield seqState + + result pipeTo replyTo + } + } + } + } + } + protected def revokeIntegrationToken(cmd: RevokeIntegrationToken): Unit = { if (!state.isAdmin(cmd.clientUserId)) { sender() ! notAdmin @@ -1159,7 +1210,7 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { // Updates that will be sent to user, when he enters group. // Helps clients that have this group to refresh it's data. - // TODO: review when chanels will be added + // TODO: review when channels will be added private def refreshGroupUpdates(newState: GroupState, userId: Int): List[Update] = List( UpdateGroupMemberChanged(groupId, isMember = true), UpdateGroupAboutChanged(groupId, newState.about), diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupErrors.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupErrors.scala index 4ac0ad30c5..a27657da21 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupErrors.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupErrors.scala @@ -19,10 +19,16 @@ object GroupErrors { case object NotAdmin extends Exception with NoStackTrace + case object NotOwner extends Exception with NoStackTrace + case object InvalidTitle extends Exception with NoStackTrace case object AboutTooLong extends Exception with NoStackTrace + case object InvalidShortName extends Exception with NoStackTrace + + case object ShortNameTaken extends Exception with NoStackTrace + case object TopicTooLong extends Exception with NoStackTrace case object NoBotFound extends Exception with NoStackTrace diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupOperations.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupOperations.scala index adcfb2810a..5ceef50cd9 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupOperations.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupOperations.scala @@ -87,6 +87,11 @@ private[group] sealed trait Commands extends UserAcl { GroupEnvelope(groupId) .withUpdateAbout(UpdateAbout(clientUserId, clientAuthId, about, randomId))).mapTo[SeqStateDate] + def updateShortName(groupId: Int, clientUserId: Int, clientAuthId: Long, shortName: Option[String]): Future[SeqState] = + (processorRegion.ref ? + GroupEnvelope(groupId) + .withUpdateShortName(UpdateShortName(clientUserId, clientAuthId, shortName))).mapTo[SeqState] + def makeUserAdmin(groupId: Int, clientUserId: Int, clientAuthId: Long, candidateId: Int): Future[(Vector[ApiMember], SeqStateDate)] = (processorRegion.ref ? GroupEnvelope(groupId) @@ -148,8 +153,9 @@ private[group] sealed trait Queries { GroupEnvelope(groupId) .withCheckAccessHash(CheckAccessHash(hash))).mapTo[CheckAccessHashResponse] map (_.isCorrect) - //(memberIds, invitedUserIds, botId) // TODO: should be signed as internal API, and become narrowly scoped + // never use it in for client queries + //(memberIds, invitedUserIds, botId) def getMemberIds(groupId: Int): Future[(Seq[Int], Seq[Int], Option[Int])] = //TODO: prepare for channel (viewRegion.ref ? GroupEnvelope(groupId) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala index 5d562402f4..f8fcb73183 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala @@ -6,6 +6,7 @@ import akka.actor.{ ActorRef, ActorSystem, Props, ReceiveTimeout, Status } import akka.cluster.sharding.ShardRegion import akka.http.scaladsl.util.FastFuture import im.actor.api.rpc.peers.{ ApiPeer, ApiPeerType } +import im.actor.concurrent.ActorFutures import im.actor.serialization.ActorSerializer import im.actor.server.cqrs.{ Processor, TaggedEvent } import im.actor.server.db.DbExtension @@ -13,6 +14,7 @@ import im.actor.server.dialog.{ DialogEnvelope, DialogExtension } import im.actor.server.group.GroupErrors.{ GroupIdAlreadyExists, GroupNotFound } import im.actor.server.group.GroupCommands._ import im.actor.server.group.GroupQueries._ +import im.actor.server.names.GlobalNamesStorageKeyValueStorage import im.actor.server.sequence.SeqUpdatesExtension import im.actor.server.user.UserExtension @@ -37,8 +39,6 @@ object GroupProcessor { 20005 → classOf[GroupCommands.Kick], 20006 → classOf[GroupCommands.Leave], 20010 → classOf[GroupCommands.UpdateAvatar], - // 20011 → classOf[GroupCommands.MakePublic], - // 20012 → classOf[GroupCommands.MakePublicAck], 20013 → classOf[GroupCommands.UpdateTitle], 20015 → classOf[GroupCommands.UpdateTopic], 20016 → classOf[GroupCommands.UpdateAbout], @@ -46,6 +46,7 @@ object GroupProcessor { 20018 → classOf[GroupCommands.RevokeIntegrationToken], 20020 → classOf[GroupCommands.RevokeIntegrationTokenAck], 20021 → classOf[GroupCommands.TransferOwnership], + 20022 → classOf[GroupCommands.UpdateShortName], 21001 → classOf[GroupQueries.GetIntegrationToken], 21002 → classOf[GroupQueries.GetIntegrationTokenResponse], @@ -80,7 +81,8 @@ object GroupProcessor { 22013 → classOf[GroupEvents.TopicUpdated], 22015 → classOf[GroupEvents.UserBecameAdmin], 22016 → classOf[GroupEvents.IntegrationTokenRevoked], - 22017 → classOf[GroupEvents.OwnerChanged] + 22017 → classOf[GroupEvents.OwnerChanged], + 22017 → classOf[GroupEvents.ShortNameUpdated] ) def persistenceIdFor(groupId: Int): String = s"Group-${groupId}" @@ -91,6 +93,7 @@ object GroupProcessor { //FIXME: snapshots!!! private[group] final class GroupProcessor extends Processor[GroupState] + with ActorFutures with GroupCommandHandlers with GroupQueryHandlers { @@ -102,6 +105,7 @@ private[group] final class GroupProcessor protected val userExt = UserExtension(system) protected var integrationStorage: IntegrationTokensWriteOps = _ + protected val globalNamesStorage = new GlobalNamesStorageKeyValueStorage protected val groupId = self.path.name.toInt protected val apiGroupPeer = ApiPeer(ApiPeerType.Group, groupId) @@ -125,6 +129,7 @@ private[group] final class GroupProcessor case u: UpdateTitle ⇒ updateTitle(u) case u: UpdateTopic ⇒ updateTopic(u) case u: UpdateAbout ⇒ updateAbout(u) + case u: UpdateShortName ⇒ updateShortName(u) // admin actions case r: RevokeIntegrationToken ⇒ revokeIntegrationToken(r) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala index 2e390f87b0..29bf454304 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala @@ -116,14 +116,20 @@ trait GroupQueryHandlers { groupId, theme = state.topic, about = state.about, - ownerUserId = state.creatorUserId, + ownerUserId = state.getShowableOwner(clientUserId), createDate = extractCreatedAtMillis(state), ext = None, canViewMembers = Some(state.canViewMembers(clientUserId)), canInvitePeople = Some(state.canInvitePeople(clientUserId)), isSharedHistory = Some(state.isHistoryShared), isAsyncMembers = Some(state.isAsyncMembers), - members = membersAndCount(state, clientUserId)._1 + members = membersAndCount(state, clientUserId)._1, + shortName = state.shortName, + canEditGroupInfo = None, + canEditShortName = None, + canEditAdminList = None, + canViewAdminList = None, + canEditAdminSettings = None ) ) } diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala index 8251037033..7e5d0a4d31 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala @@ -33,6 +33,7 @@ private[group] object GroupState { about = None, avatar = None, topic = None, + shortName = None, typ = GroupType.General, isHidden = false, isHistoryShared = false, @@ -59,6 +60,7 @@ private[group] final case class GroupState( about: Option[String], avatar: Option[Avatar], topic: Option[String], + shortName: Option[String], typ: GroupType, // TODO: rename to groupType isHidden: Boolean, isHistoryShared: Boolean, @@ -122,6 +124,12 @@ private[group] final case class GroupState( case Channel ⇒ true } + def getShowableOwner(clientUserId: Int): Option[Int] = + typ match { + case General | Public ⇒ Some(creatorUserId) + case Channel ⇒ if (isAdmin(clientUserId)) Some(creatorUserId) else None + } + override def updated(e: Event): GroupState = e match { case evt: Created ⇒ this.copy( @@ -218,6 +226,8 @@ private[group] final case class GroupState( ) case OwnerChanged(_, userId) ⇒ this.copy(ownerUserId = userId) + case ShortNameUpdated(_, newShortName) ⇒ + this.copy(shortName = newShortName) } // TODO: real snapshot diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/office/PushTexts.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/PushTexts.scala similarity index 91% rename from actor-server/actor-core/src/main/scala/im/actor/server/office/PushTexts.scala rename to actor-server/actor-core/src/main/scala/im/actor/server/group/PushTexts.scala index a270327262..674d313ea7 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/office/PushTexts.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/PushTexts.scala @@ -1,4 +1,4 @@ -package im.actor.server.office +package im.actor.server.group //TODO: make up to date with channels object PushTexts { diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/names/GlobalNamesStorage.scala b/actor-server/actor-core/src/main/scala/im/actor/server/names/GlobalNamesStorage.scala new file mode 100644 index 0000000000..261cf44223 --- /dev/null +++ b/actor-server/actor-core/src/main/scala/im/actor/server/names/GlobalNamesStorage.scala @@ -0,0 +1,110 @@ +package im.actor.server.names + +import akka.actor.ActorSystem +import akka.http.scaladsl.util.FastFuture +import im.actor.server.db.DbExtension +import im.actor.server.persist.UserRepo +import im.actor.storage.SimpleStorage +import slick.dbio._ + +import scala.concurrent.Future + +/** + * Stores mapping "Global name" -> "Global name owner(group/user)" + * global name: String + * global name owner: GlobalNameOwner + */ +private object GlobalNamesStorage extends SimpleStorage("global_names") + +/** + * Storage that keeps compatibility between + * storing nicknames in `im.actor.server.persist.UserRepo` + * and storing group and user names in new `im.actor.storage.SimpleStorage` storage + */ +final class GlobalNamesStorageKeyValueStorage(implicit system: ActorSystem) { + import system.dispatcher + + private val (db, conn) = { + val ext = DbExtension(system) + (ext.db, ext.connector) + } + + def getUserOwnerId(name: String): Future[Option[Int]] = + getOwner(name) map (_.collect { + case GlobalNameOwner(OwnerType.User, userId) ⇒ userId + }) + + def getGroupOwnerId(name: String): Future[Option[Int]] = + getOwner(name) map (_.collect { + case GlobalNameOwner(OwnerType.Group, groupId) ⇒ groupId + }) + + /** + * Compatible with storing nicknames in `im.actor.server.persist.UserRepo` + */ + def exists(name: String): Future[Boolean] = { + val existsInKV = conn.run(GlobalNamesStorage.get(name)) map (_.isDefined) + + existsInKV flatMap { + case true ⇒ FastFuture.successful(true) + case false ⇒ db.run(UserRepo.nicknameExists(name)) + } + } + + /** + * `oldGlobalName` = None, `newGlobalName` = Some("name") - insert new name + * `oldGlobalName` = Some("oldName"), `newGlobalName` = Some("name") - update existing name + * `oldGlobalName` = Some("oldName"), `newGlobalName` = None - delete existing name + */ + def updateOrRemove(oldGlobalName: Option[String], newGlobalName: Option[String], owner: GlobalNameOwner): Future[Unit] = { + val deleteFu = (oldGlobalName map delete) getOrElse FastFuture.successful(()) + val upsertFu = (newGlobalName map (n ⇒ upsert(n, owner))) getOrElse FastFuture.successful(()) + for { + _ ← deleteFu + _ ← upsertFu + } yield () + } + + /** + * Compatible with storing nicknames in `im.actor.server.persist.UserRepo` + */ + private def getOwner(name: String): Future[Option[GlobalNameOwner]] = { + val optOwner = conn.run( + GlobalNamesStorage.get(name) + ) map { optBytes ⇒ + optBytes map GlobalNameOwner.parseFrom + } + optOwner flatMap { + case o @ Some(_) ⇒ FastFuture.successful(o) + case None ⇒ db.run(UserRepo.findByNickname(name)) map (_.map(u ⇒ GlobalNameOwner(OwnerType.User, u.id))) + } + } + + private def upsert(name: String, owner: GlobalNameOwner): Future[Unit] = + conn.run( + GlobalNamesStorage.upsert(name, owner.toByteArray) + ) map (_ ⇒ ()) + + /** + * Compatible with storing nicknames in `im.actor.server.persist.UserRepo` + */ + private def delete(name: String): Future[Unit] = { + val kvDelete = conn.run(GlobalNamesStorage.delete(name)) + + kvDelete flatMap { count ⇒ + if (count > 0) { + db.run { + for { + optUser ← UserRepo.findByNickname(name) + _ ← optUser match { + case Some(u) ⇒ UserRepo.setNickname(u.id, None) + case None ⇒ DBIO.successful(0) + } + } yield () + } + } else { + FastFuture.successful(()) + } + } + } +} diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/user/UserCommandHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/user/UserCommandHandlers.scala index e6d1cccdde..811147f640 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/user/UserCommandHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/user/UserCommandHandlers.scala @@ -19,6 +19,7 @@ import im.actor.server.bots.BotCommand import im.actor.server.file.{ Avatar, ImageUtils } import im.actor.server.model.{ AvatarData, Sex, User } import im.actor.server.model.contact.{ UserContact, UserEmailContact, UserPhoneContact } +import im.actor.server.names.{ GlobalNameOwner, OwnerType } import im.actor.server.office.EntityNotFound import im.actor.server.persist.contact._ import im.actor.server.persist._ @@ -217,12 +218,12 @@ private[user] trait UserCommandHandlers { onSuccess(checkNicknameExists(nicknameOpt)) { exists ⇒ if (!exists) { - if (nicknameOpt forall StringUtils.validUsername) { + if (nicknameOpt forall StringUtils.validGlobalName) { persistReply(UserEvents.NicknameChanged(now(), nicknameOpt), user, replyTo) { _ ⇒ val update = UpdateUserNickChanged(userId, nicknameOpt) for { - _ ← db.run(UserRepo.setNickname(userId, nicknameOpt)) + _ ← globalNamesStorage.updateOrRemove(user.nickname, nicknameOpt, GlobalNameOwner(OwnerType.User, userId)) relatedUserIds ← getRelations(userId) seqState ← seqUpdExt.broadcastClientUpdate(userId, authId, relatedUserIds, update) } yield seqState @@ -429,7 +430,7 @@ private[user] trait UserCommandHandlers { private def checkNicknameExists(nicknameOpt: Option[String]): Future[Boolean] = { nicknameOpt match { - case Some(nickname) ⇒ db.run(UserRepo.nicknameExists(nickname)) + case Some(nickname) ⇒ globalNamesStorage.exists(nickname) case None ⇒ FastFuture.successful(false) } } diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/user/UserProcessor.scala b/actor-server/actor-core/src/main/scala/im/actor/server/user/UserProcessor.scala index a9269035ef..d0c2af3c7c 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/user/UserProcessor.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/user/UserProcessor.scala @@ -15,6 +15,7 @@ import im.actor.server.cqrs.TaggedEvent import im.actor.server.db.DbExtension import im.actor.server.dialog._ import im.actor.server.model.{ Peer, PeerType } +import im.actor.server.names.GlobalNamesStorageKeyValueStorage import im.actor.server.office.{ PeerProcessor, StopOffice } import im.actor.server.sequence.SeqUpdatesExtension import im.actor.server.social.{ SocialExtension, SocialManagerRegion } @@ -161,6 +162,7 @@ private[user] final class UserProcessor protected lazy val dialogExt = DialogExtension(system) protected val seqUpdExt: SeqUpdatesExtension = SeqUpdatesExtension(system) protected implicit val socialRegion: SocialManagerRegion = SocialExtension(system).region + protected val globalNamesStorage = new GlobalNamesStorageKeyValueStorage protected implicit val timeout: Timeout = Timeout(10.seconds) diff --git a/actor-server/actor-persist/src/main/scala/im/actor/server/persist/UserRepo.scala b/actor-server/actor-persist/src/main/scala/im/actor/server/persist/UserRepo.scala index bc649450e5..efea1aa4e7 100644 --- a/actor-server/actor-persist/src/main/scala/im/actor/server/persist/UserRepo.scala +++ b/actor-server/actor-persist/src/main/scala/im/actor/server/persist/UserRepo.scala @@ -40,11 +40,11 @@ object UserRepo { val byIdC = Compiled(byId _) val nameByIdC = Compiled(nameById _) - def byNickname(nickname: Rep[String]) = users filter (_.nickname.toLowerCase === nickname.toLowerCase) - def idsByNickname(nickname: Rep[String]) = byNickname(nickname).map(_.id) + private def byNickname(nickname: Rep[String]) = users filter (_.nickname.toLowerCase === nickname.toLowerCase) + private def idsByNickname(nickname: Rep[String]) = byNickname(nickname).map(_.id) - val byNicknameC = Compiled(byNickname _) - val idsByNicknameC = Compiled(idsByNickname _) + private val byNicknameC = Compiled(byNickname _) + private val idsByNicknameC = Compiled(idsByNickname _) def byPhone(phone: Rep[Long]) = (for { phones ← UserPhoneRepo.phones.filter(_.number === phone) @@ -106,15 +106,13 @@ object UserRepo { def findSalts(ids: Set[Int]) = users.filter(_.id inSet ids).map(u ⇒ (u.id, u.accessSalt)).result + @deprecated("user GlobalNamesStorageKeyValueStorage instead", "2016-07-17") def findByNickname(query: String) = { val nickname = if (query.startsWith("@")) query.drop(1) else query byNicknameC(nickname).result.headOption } - def findIdsByNickname(nickname: String) = - idsByNicknameC(nickname).result.headOption - def findIdsByEmail(email: String) = idsByEmailC(email).result.headOption @@ -128,12 +126,14 @@ object UserRepo { .getOrElse(DBIO.successful(Nil)) } yield e ++ n ++ p + @deprecated("user GlobalNamesStorageKeyValueStorage instead", "2016-07-17") def setNickname(userId: Int, nickname: Option[String]) = byId(userId).map(_.nickname).update(nickname) def setAbout(userId: Int, about: Option[String]) = byId(userId).map(_.about).update(about) + @deprecated("user GlobalNamesStorageKeyValueStorage instead", "2016-07-17") def nicknameExists(nickname: String) = users.filter(_.nickname.toLowerCase === nickname.toLowerCase).exists.result diff --git a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/auth/AuthHelpers.scala b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/auth/AuthHelpers.scala index 01ba65d1b5..4b2dd15804 100644 --- a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/auth/AuthHelpers.scala +++ b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/auth/AuthHelpers.scala @@ -16,7 +16,6 @@ import im.actor.server.model._ import im.actor.server.persist._ import im.actor.server.persist.auth.AuthTransactionRepo import im.actor.server.session._ -import im.actor.util.misc.EmailUtils.isTestEmail import im.actor.util.misc.IdUtils._ import im.actor.util.misc.PhoneNumberUtils._ import im.actor.util.misc.StringUtils.validName @@ -25,7 +24,6 @@ import org.joda.time.DateTime import slick.dbio._ import scala.concurrent.Future -import scala.util.Try trait AuthHelpers extends Helpers { self: AuthServiceImpl ⇒ @@ -69,10 +67,10 @@ trait AuthHelpers extends Helpers { protected def newUsernameSignUp(transaction: AuthUsernameTransaction, name: String, sex: Option[ApiSex]): Result[(Int, String) Xor User] = { val username = transaction.username for { - optUser ← fromDBIO(UserRepo.findByNickname(username)) - result ← optUser match { - case Some(existingUser) ⇒ point(Xor.left((existingUser.id, ""))) - case None ⇒ newUser(name, "", sex, username = Some(username)) + optUserId ← fromFuture(globalNamesStorage.getUserOwnerId(username)) + result ← optUserId match { + case Some(id) ⇒ point(Xor.left((id, ""))) + case None ⇒ newUser(name, "", sex, username = Some(username)) } } yield result } @@ -170,8 +168,8 @@ trait AuthHelpers extends Helpers { } yield (emailModel.userId, "") case u: AuthUsernameTransaction ⇒ for { - userModel ← fromDBIOOption(AuthErrors.UsernameUnoccupied)(UserRepo.findByNickname(u.username)) - } yield (userModel.id, "") + userId ← fromFutureOption(AuthErrors.UsernameUnoccupied)(globalNamesStorage.getUserOwnerId(u.username)) + } yield (userId, "") case _: AuthAnonymousTransaction ⇒ fromEither(Xor.left(AuthErrors.NotValidated)) } diff --git a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/auth/AuthServiceImpl.scala b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/auth/AuthServiceImpl.scala index 6c44c5b861..4f49ce914c 100644 --- a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/auth/AuthServiceImpl.scala +++ b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/auth/AuthServiceImpl.scala @@ -21,9 +21,10 @@ import im.actor.server.auth.DeviceInfo import im.actor.server.db.DbExtension import im.actor.server.email.{ EmailConfig, SmtpEmailSender } import im.actor.server.model._ +import im.actor.server.names.GlobalNamesStorageKeyValueStorage import im.actor.server.oauth.GoogleProvider import im.actor.server.persist._ -import im.actor.server.persist.auth.{ AuthUsernameTransactionRepo, AuthPhoneTransactionRepo, AuthTransactionRepo, AuthEmailTransactionRepo } +import im.actor.server.persist.auth.{ AuthEmailTransactionRepo, AuthPhoneTransactionRepo, AuthTransactionRepo, AuthUsernameTransactionRepo } import im.actor.server.session._ import im.actor.server.social.{ SocialExtension, SocialManagerRegion } import im.actor.server.user.{ UserErrors, UserExtension } @@ -59,6 +60,7 @@ final class AuthServiceImpl(val oauth2Service: GoogleProvider)( protected val userExt = UserExtension(actorSystem) protected implicit val socialRegion: SocialManagerRegion = SocialExtension(actorSystem).region protected val activationContext = new ActivationContext + protected val globalNamesStorage = new GlobalNamesStorageKeyValueStorage private implicit val mat = ActorMaterializer() @@ -200,8 +202,8 @@ final class AuthServiceImpl(val oauth2Service: GoogleProvider)( val action = for { normUsername ← fromOption(ProfileRpcErrors.NicknameInvalid)(StringUtils.normalizeUsername(username)) - optUser ← fromDBIO(UserRepo.findByNickname(username)) - _ ← optUser map (u ⇒ forbidDeletedUser(u.id)) getOrElse point(()) + optUserId ← fromFuture(globalNamesStorage.getUserOwnerId(username)) + _ ← optUserId map (id ⇒ forbidDeletedUser(id)) getOrElse point(()) optAuthTransaction ← fromDBIO(AuthUsernameTransactionRepo.find(username, deviceHash)) transactionHash ← optAuthTransaction match { case Some(transaction) ⇒ point(transaction.transactionHash) @@ -210,7 +212,7 @@ final class AuthServiceImpl(val oauth2Service: GoogleProvider)( val transactionHash = ACLUtils.authTransactionHash(accessSalt) val authTransaction = AuthUsernameTransaction( normUsername, - optUser map (_.id), + optUserId, transactionHash, appId, apiKey, @@ -218,11 +220,11 @@ final class AuthServiceImpl(val oauth2Service: GoogleProvider)( deviceTitle, accessSalt, DeviceInfo(timeZone.getOrElse(""), preferredLanguages).toByteArray, - isChecked = optUser.isEmpty // we don't need to check password if user signs up + isChecked = optUserId.isEmpty // we don't need to check password if user signs up ) for (_ ← fromDBIO(AuthUsernameTransactionRepo.create(authTransaction))) yield transactionHash } - } yield ResponseStartUsernameAuth(transactionHash, optUser.isDefined) + } yield ResponseStartUsernameAuth(transactionHash, optUserId.isDefined) db.run(action.value) } @@ -241,7 +243,7 @@ final class AuthServiceImpl(val oauth2Service: GoogleProvider)( for { normUsername ← fromOption(ProfileRpcErrors.NicknameInvalid)(StringUtils.normalizeUsername(username)) accessSalt = ACLUtils.nextAccessSalt() - nicknameExists ← fromDBIO(UserRepo.nicknameExists(normUsername)) + nicknameExists ← fromFuture(globalNamesStorage.exists(normUsername)) _ ← fromBoolean(ProfileRpcErrors.NicknameBusy)(!nicknameExists) transactionHash = ACLUtils.authTransactionHash(accessSalt) transaction = AuthAnonymousTransaction( diff --git a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/contacts/ContactsServiceImpl.scala b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/contacts/ContactsServiceImpl.scala index 58a6d43240..362e3fc3ac 100644 --- a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/contacts/ContactsServiceImpl.scala +++ b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/contacts/ContactsServiceImpl.scala @@ -24,6 +24,7 @@ import im.actor.api.rpc.misc._ import im.actor.api.rpc.sequence.ApiUpdateOptimization import im.actor.api.rpc.users.ApiUser import im.actor.server.db.DbExtension +import im.actor.server.names.GlobalNamesStorageKeyValueStorage import im.actor.server.sequence.{ SeqState, SeqUpdatesExtension } import im.actor.server.social.{ SocialExtension, SocialManager, SocialManagerRegion } import im.actor.server.user._ @@ -49,6 +50,7 @@ class ContactsServiceImpl(implicit actorSystem: ActorSystem) private val userExt = UserExtension(actorSystem) private implicit val seqUpdExt: SeqUpdatesExtension = SeqUpdatesExtension(actorSystem) private implicit val socialRegion: SocialManagerRegion = SocialExtension(actorSystem).region + private val globalNamesStorage = new GlobalNamesStorageKeyValueStorage case class EmailNameUser(email: String, name: Option[String], userId: Int) @@ -170,8 +172,8 @@ class ContactsServiceImpl(implicit actorSystem: ActorSystem) private def findByNickname(nickname: String, client: AuthorizedClientData): Result[Vector[ApiUser]] = { for { - users ← fromDBIO(UserRepo.findByNickname(nickname) map (_.toList)) - structs ← fromFuture(Future.sequence(users map (user ⇒ userExt.getApiStruct(user.id, client.userId, client.authId)))) + optUserId ← fromFuture(globalNamesStorage.getUserOwnerId(nickname)) + structs ← fromFuture(Future.sequence(optUserId.toSeq map (userId ⇒ userExt.getApiStruct(userId, client.userId, client.authId)))) } yield structs.toVector } diff --git a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupRpcErrors.scala b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupRpcErrors.scala index d40f68de22..5949a8883a 100644 --- a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupRpcErrors.scala +++ b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupRpcErrors.scala @@ -12,8 +12,14 @@ object GroupRpcErrors { val AboutTooLong = RpcError(400, "GROUP_ABOUT_TOO_LONG", "Group about is too long. It should be no longer then 255 characters", false, None) val UserAlreadyAdmin = RpcError(400, "USER_ALREADY_ADMIN", "User is already admin of this group", false, None) val BlockedByUser = RpcError(403, "BLOCKED_BY_USER", "User blocked you, unable to invite him.", false, None) - val GroupIdAlreadyExists = RpcError(400, "GROUP_ALREADY_EXISTS", "Group with such id already exists", false, None) - val InvalidInviteToken = RpcError(403, "INVALID_INVITE_TOKEN", "No correct token provided.", false, None) + val GroupIdAlreadyExists = RpcError(400, "GROUP_ALREADY_EXISTS", "Group with such id already exists.", false, None) + val InvalidInviteUrl = RpcError(403, "INVALID_INVITE_URL", "Invalid invite url!", false, None) + val InvalidInviteToken = RpcError(403, "INVALID_INVITE_TOKEN", "Invalid invite token!", false, None) + val InvalidInviteGroup = RpcError(403, "INVALID_INVITE_GROUP", "Invalid group name provided!", false, None) val GroupNotPublic = RpcError(400, "GROUP_IS_NOT_PUBLIC", "The group is not public.", false, None) + val InvalidShortName = RpcError(400, "GROUP_SHORT_NAME_INVALID", + "Invalid group short name. Valid short name should contain from 5 to 32 characters, and may consist of latin characters, numbers and underscores", false, None) + val ShortNameTaken = RpcError(400, "GROUP_SHORT_NAME_TAKEN", "This short name already belongs to other user or group, we are sorry!", false, None) + } // format: ON diff --git a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupsServiceImpl.scala b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupsServiceImpl.scala index b83512acb5..8b58909a7a 100644 --- a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupsServiceImpl.scala +++ b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupsServiceImpl.scala @@ -4,11 +4,12 @@ import java.time.Instant import akka.actor.ActorSystem import akka.http.scaladsl.util.FastFuture +import cats.data.Xor import im.actor.api.rpc.PeerHelpers._ import im.actor.api.rpc._ import im.actor.api.rpc.files.ApiFileLocation import im.actor.api.rpc.groups._ -import im.actor.api.rpc.misc.ResponseSeqDate +import im.actor.api.rpc.misc.{ ResponseSeq, ResponseSeqDate, ResponseVoid } import im.actor.api.rpc.peers.{ ApiGroupOutPeer, ApiUserOutPeer } import im.actor.api.rpc.sequence.ApiUpdateOptimization import im.actor.api.rpc.users.ApiUser @@ -18,12 +19,13 @@ import im.actor.server.db.DbExtension import im.actor.server.file.{ FileErrors, FileStorageAdapter, FileStorageExtension, ImageUtils } import im.actor.server.group._ import im.actor.server.model.GroupInviteToken +import im.actor.server.names.GlobalNamesStorageKeyValueStorage import im.actor.server.persist.{ GroupInviteTokenRepo, GroupUserRepo } import im.actor.server.presences.GroupPresenceExtension import im.actor.server.sequence.{ SeqState, SeqStateDate, SeqUpdatesExtension } import im.actor.server.user.UserExtension import im.actor.util.ThreadLocalSecureRandom -import im.actor.util.misc.IdUtils +import im.actor.util.misc.{ IdUtils, StringUtils } import slick.dbio.DBIO import slick.driver.PostgresDriver.api._ @@ -44,6 +46,7 @@ final class GroupsServiceImpl(groupInviteConfig: GroupInviteConfig)(implicit act private val seqUpdExt = SeqUpdatesExtension(actorSystem) private val userExt = UserExtension(actorSystem) private val groupPresenceExt = GroupPresenceExtension(actorSystem) + private val globalNamesStorage = new GlobalNamesStorageKeyValueStorage /** * Loading Full Groups @@ -80,6 +83,15 @@ final class GroupsServiceImpl(groupInviteConfig: GroupInviteConfig)(implicit act } } + override def doHandleDismissUserAdmin(groupPeer: ApiGroupOutPeer, userPeer: ApiUserOutPeer, clientData: ClientData): Future[HandlerResult[ResponseSeq]] = + FastFuture.failed(new RuntimeException("Not implemented")) + + override def doHandleLoadAdminSettings(groupPeer: ApiGroupOutPeer, clientData: ClientData): Future[HandlerResult[ResponseLoadAdminSettings]] = + FastFuture.failed(new RuntimeException("Not implemented")) + + override def doHandleSaveAdminSettings(groupPeer: ApiGroupOutPeer, settings: ApiAdminSettings, clientData: ClientData): Future[HandlerResult[ResponseVoid]] = + FastFuture.failed(new RuntimeException("Not implemented")) + /** * Loading group members * @@ -320,26 +332,36 @@ final class GroupsServiceImpl(groupInviteConfig: GroupInviteConfig)(implicit act } override def doHandleJoinGroup( - urlOrToken: String, - optimizations: IndexedSeq[ApiUpdateOptimization.Value], - clientData: ClientData + joinStringOrUrl: String, + optimizations: IndexedSeq[ApiUpdateOptimization.Value], + clientData: ClientData ): Future[HandlerResult[ResponseJoinGroup]] = authorized(clientData) { implicit client ⇒ addOptimizations(optimizations) - val token = extractInviteToken(urlOrToken) val stripEntities = optimizations.contains(ApiUpdateOptimization.STRIP_ENTITIES) val action = for { - tokenInfo ← fromFutureOption(GroupRpcErrors.InvalidInviteToken)(db.run(GroupInviteTokenRepo.findByToken(token))) + joinSting ← fromOption(GroupRpcErrors.InvalidInviteUrl)(extractJoinString(joinStringOrUrl)) + joinInfo ← joinSting match { + case Xor.Left(token) ⇒ + for { + info ← fromFutureOption(GroupRpcErrors.InvalidInviteToken)(db.run(GroupInviteTokenRepo.findByToken(token))) + } yield info.groupId → Some(info.creatorId) + case Xor.Right(groupName) ⇒ + for { + groupId ← fromFutureOption(GroupRpcErrors.InvalidInviteGroup)(globalNamesStorage.getGroupOwnerId(groupName)) + } yield groupId → None + } + (groupId, optInviter) = joinInfo joinResp ← fromFuture(groupExt.joinGroup( - groupId = tokenInfo.groupId, + groupId = groupId, joiningUserId = client.userId, joiningUserAuthId = client.authId, - invitingUserId = Some(tokenInfo.creatorId) + invitingUserId = optInviter )) ((SeqStateDate(seq, state, date), userIds, randomId)) = joinResp usersPeers ← fromFuture(usersOrPeers(userIds, stripEntities)) - groupStruct ← fromFuture(groupExt.getApiStruct(tokenInfo.groupId, client.userId)) + groupStruct ← fromFuture(groupExt.getApiStruct(groupId, client.userId)) } yield ResponseJoinGroup( groupStruct, seq, @@ -408,6 +430,15 @@ final class GroupsServiceImpl(groupInviteConfig: GroupInviteConfig)(implicit act } } + override def doHandleEditGroupShortName(groupPeer: ApiGroupOutPeer, shortName: Option[String], clientData: ClientData): Future[HandlerResult[ResponseSeq]] = + authorized(clientData) { client ⇒ + withGroupOutPeer(groupPeer) { + for { + SeqState(seq, state) ← groupExt.updateShortName(groupPeer.groupId, client.userId, client.authId, shortName) + } yield Ok(ResponseSeq(seq, state.toByteArray)) + } + } + private def usersOrPeers(userIds: Vector[Int], stripEntities: Boolean)(implicit client: AuthorizedClientData): Future[(Vector[ApiUser], Vector[ApiUserOutPeer])] = if (stripEntities) { val users = Vector.empty[ApiUser] @@ -427,11 +458,20 @@ final class GroupsServiceImpl(groupInviteConfig: GroupInviteConfig)(implicit act private def genInviteUrl(token: String) = s"$inviteUriBase$token" - private def extractInviteToken(urlOrToken: String) = - if (urlOrToken.startsWith(groupInviteConfig.baseUrl)) - urlOrToken.drop(inviteUriBase.length).takeWhile(c ⇒ c != '?' && c != '#') + private def extractJoinString(urlOrTokenOrGroupName: String): Option[String Xor String] = { + val extracted = if (urlOrTokenOrGroupName.startsWith(groupInviteConfig.baseUrl)) + urlOrTokenOrGroupName.drop(inviteUriBase.length).takeWhile(c ⇒ c != '?' && c != '#') else - urlOrToken + urlOrTokenOrGroupName + + if (StringUtils.validGroupInviteToken(extracted)) { + Some(Xor.left(extracted)) + } else if (StringUtils.validGlobalName(extracted)) { + Some(Xor.right(extracted)) + } else { + None + } + } private def addOptimizations(opts: IndexedSeq[ApiUpdateOptimization.Value])(implicit client: AuthorizedClientData): Unit = seqUpdExt.addOptimizations(client.userId, client.authId, opts map (_.id)) @@ -515,7 +555,8 @@ final class GroupsServiceImpl(groupInviteConfig: GroupInviteConfig)(implicit act override def onFailure: PartialFunction[Throwable, RpcError] = recoverCommon orElse { case GroupErrors.NotAMember ⇒ CommonRpcErrors.forbidden("Not a group member!") - case GroupErrors.NotAdmin ⇒ CommonRpcErrors.forbidden("Only admin can perform this action.") + case GroupErrors.NotAdmin ⇒ CommonRpcErrors.forbidden("Only group admin can perform this action.") + case GroupErrors.NotOwner ⇒ CommonRpcErrors.forbidden("Only group owner can perform this action.") case GroupErrors.UserAlreadyAdmin ⇒ GroupRpcErrors.UserAlreadyAdmin case GroupErrors.InvalidTitle ⇒ GroupRpcErrors.InvalidTitle case GroupErrors.AboutTooLong ⇒ GroupRpcErrors.AboutTooLong @@ -525,6 +566,8 @@ final class GroupsServiceImpl(groupInviteConfig: GroupInviteConfig)(implicit act case GroupErrors.UserAlreadyInvited ⇒ GroupRpcErrors.AlreadyInvited case GroupErrors.UserAlreadyJoined ⇒ GroupRpcErrors.AlreadyJoined case GroupErrors.GroupIdAlreadyExists(_) ⇒ GroupRpcErrors.GroupIdAlreadyExists + case GroupErrors.InvalidShortName ⇒ GroupRpcErrors.InvalidShortName + case GroupErrors.ShortNameTaken ⇒ GroupRpcErrors.ShortNameTaken } } diff --git a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/profile/ProfileServiceImpl.scala b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/profile/ProfileServiceImpl.scala index fa9c627bd9..f606f3c410 100644 --- a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/profile/ProfileServiceImpl.scala +++ b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/profile/ProfileServiceImpl.scala @@ -7,17 +7,15 @@ import im.actor.api.rpc.files.ApiFileLocation import im.actor.api.rpc.misc.{ ResponseBool, ResponseSeq } import im.actor.api.rpc.profile.{ ProfileService, ResponseEditAvatar } import im.actor.server.db.DbExtension -import im.actor.server.file.{ FileStorageExtension, FileErrors, FileStorageAdapter, ImageUtils } -import im.actor.server.persist.UserRepo -import im.actor.server.sequence.{ SequenceErrors, SeqState } +import im.actor.server.file.{ FileErrors, FileStorageAdapter, FileStorageExtension, ImageUtils } +import im.actor.server.names.GlobalNamesStorageKeyValueStorage +import im.actor.server.sequence.{ SeqState, SequenceErrors } import im.actor.server.social.{ SocialExtension, SocialManagerRegion } import im.actor.server.user._ -import im.actor.util.ThreadLocalSecureRandom import im.actor.util.misc.StringUtils import slick.driver.PostgresDriver.api._ import scala.concurrent.duration._ -import scala.concurrent.forkjoin.ThreadLocalRandom import scala.concurrent.{ ExecutionContext, Future } object ProfileRpcErrors { @@ -44,6 +42,7 @@ final class ProfileServiceImpl()(implicit system: ActorSystem) extends ProfileSe private val userExt = UserExtension(system) private implicit val socialRegion: SocialManagerRegion = SocialExtension(system).region private implicit val fsAdapter: FileStorageAdapter = FileStorageExtension(system).fsAdapter + private val globalNamesStorage = new GlobalNamesStorageKeyValueStorage // TODO: flatten override def doHandleEditAvatar(fileLocation: ApiFileLocation, clientData: ClientData): Future[HandlerResult[ResponseEditAvatar]] = @@ -90,12 +89,12 @@ final class ProfileServiceImpl()(implicit system: ActorSystem) extends ProfileSe override def doHandleCheckNickName(nickname: String, clientData: ClientData): Future[HandlerResult[ResponseBool]] = authorized(clientData) { implicit client ⇒ (for { - _ ← fromBoolean(ProfileRpcErrors.NicknameInvalid)(StringUtils.validUsername(nickname)) - exists ← fromFuture(db.run(UserRepo.nicknameExists(nickname.trim))) + _ ← fromBoolean(ProfileRpcErrors.NicknameInvalid)(StringUtils.validGlobalName(nickname)) + exists ← fromFuture(globalNamesStorage.exists(nickname.trim)) } yield ResponseBool(!exists)).value } - //todo: move validation inside of UserOffice + //todo: move validation inside of UserProcessor override def doHandleEditAbout(about: Option[String], clientData: ClientData): Future[HandlerResult[ResponseSeq]] = { authorized(clientData) { implicit client ⇒ (for { diff --git a/actor-server/actor-runtime/src/main/scala/im/actor/util/misc/StringUtils.scala b/actor-server/actor-runtime/src/main/scala/im/actor/util/misc/StringUtils.scala index 5f5395ab37..47118e45d2 100644 --- a/actor-server/actor-runtime/src/main/scala/im/actor/util/misc/StringUtils.scala +++ b/actor-server/actor-runtime/src/main/scala/im/actor/util/misc/StringUtils.scala @@ -14,6 +14,8 @@ object StringUtils { private val usernamePattern = Pattern.compile("""^[0-9a-zA-Z_]{5,32}""", Pattern.UNICODE_CHARACTER_CLASS) + private val sha256Pattern = Pattern.compile("^[A-Fa-f0-9]{64}$", Pattern.UNICODE_CHARACTER_CLASS) + private val transliterator = Transliterator.getInstance("Latin; Latin-ASCII") def utfToHexString(s: String): String = { s.map(ch ⇒ f"${ch.toInt}%04X").mkString } @@ -37,7 +39,9 @@ object StringUtils { def validName(n: String): NonEmptyList[String] Xor String = nonEmptyString(n).flatMap(printableString) - def validUsername(username: String): Boolean = usernamePattern.matcher(username.trim).matches + def validGlobalName(username: String): Boolean = usernamePattern.matcher(username.trim).matches + + def validGroupInviteToken(token: String): Boolean = sha256Pattern.matcher(token.trim).matches def normalizeUsername(username: String): Option[String] = { val trimmed = username.trim diff --git a/actor-server/actor-tests/src/test/scala/im/actor/util/StringUtilsSpec.scala b/actor-server/actor-tests/src/test/scala/im/actor/util/StringUtilsSpec.scala index 7b25b68cf5..945e7a17cd 100644 --- a/actor-server/actor-tests/src/test/scala/im/actor/util/StringUtilsSpec.scala +++ b/actor-server/actor-tests/src/test/scala/im/actor/util/StringUtilsSpec.scala @@ -1,6 +1,6 @@ package im.actor.util -import im.actor.util.misc.StringUtils.{ transliterate, validUsername } +import im.actor.util.misc.StringUtils.{ transliterate, validGlobalName } import org.scalatest.{ Matchers, FlatSpecLike } class StringUtilsSpec extends FlatSpecLike with Matchers { @@ -10,19 +10,19 @@ class StringUtilsSpec extends FlatSpecLike with Matchers { "transliterate" should "transform string to lower-cased string with only latin chars" in translit def nicknames() = { - validUsername("rockjam") shouldEqual true - validUsername("abcde") shouldEqual true - validUsername("rock_jam") shouldEqual true - validUsername("r0ck_jaM___") shouldEqual true + validGlobalName("rockjam") shouldEqual true + validGlobalName("abcde") shouldEqual true + validGlobalName("rock_jam") shouldEqual true + validGlobalName("r0ck_jaM___") shouldEqual true //too long val tooLong = 0 to 35 map (e ⇒ ".") mkString "" - validUsername(tooLong) shouldEqual false + validGlobalName(tooLong) shouldEqual false //too short - validUsername("roc") shouldEqual false + validGlobalName("roc") shouldEqual false //wrong symbols - validUsername("rock-jam") shouldEqual false - validUsername("rock&^^jam") shouldEqual false + validGlobalName("rock-jam") shouldEqual false + validGlobalName("rock&^^jam") shouldEqual false } def translit() = { From 296fe2ee927c1fd3b02a9918b2202fa1a2863605 Mon Sep 17 00:00:00 2001 From: rockjam Date: Mon, 18 Jul 2016 03:17:55 +0300 Subject: [PATCH 138/414] fix(server:groups): duplicate id for serializer --- .../src/main/scala/im/actor/server/group/GroupProcessor.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala index f8fcb73183..a37f813d9d 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala @@ -82,7 +82,7 @@ object GroupProcessor { 22015 → classOf[GroupEvents.UserBecameAdmin], 22016 → classOf[GroupEvents.IntegrationTokenRevoked], 22017 → classOf[GroupEvents.OwnerChanged], - 22017 → classOf[GroupEvents.ShortNameUpdated] + 22018 → classOf[GroupEvents.ShortNameUpdated] ) def persistenceIdFor(groupId: Int): String = s"Group-${groupId}" From 05a9752f4958563a3f470602c84b72171d97bd7b Mon Sep 17 00:00:00 2001 From: rockjam Date: Mon, 18 Jul 2016 03:26:50 +0300 Subject: [PATCH 139/414] fix(server: groups): seq optimization for new updates, remove service messages for inviter in channels --- .../server/group/GroupCommandHandlers.scala | 35 +++---------------- .../actor/server/sequence/Optimization.scala | 9 ++++- 2 files changed, 12 insertions(+), 32 deletions(-) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala index 16c3ff671e..16eb72e539 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala @@ -271,9 +271,9 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { deliveryId = s"useradded_${groupId}_${cmd.randomId}" ) - // push service message to invitee and inviter - _ ← seqUpdExt.broadcastPeopleUpdate( - userIds = Set(cmd.inviterUserId, cmd.inviteeUserId), + // push service message to invitee + _ ← seqUpdExt.deliverUserUpdate( + userId = cmd.inviteeUserId, update = serviceMessageUpdate( cmd.inviterUserId, dateMillis, @@ -330,7 +330,7 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { /** * User can join * • after invite(was invited by other user previously). In this case he already have group on devices - * • via invite ling. In this case he doesn't have group, and we need to deliver it. + * • via invite link. In this case he doesn't have group, and we need to deliver it. */ protected def join(cmd: Join): Unit = { // user is already a member, and should not complete invitation process @@ -443,18 +443,6 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { membersUpdateNew, deliveryId = s"userjoined_${groupId}_${randomId}" ) - - // push service message only to inviter - _ ← seqUpdExt.deliverUserUpdate( - userId = inviterUserId, - update = serviceMessageUpdate( - cmd.joiningUserId, - dateMillis, - randomId, - serviceMessage - ), - deliveryTag = Some(Optimization.GroupV2) - ) } yield SeqStateDate(seq, state, dateMillis) val result: Future[(SeqStateDate, Vector[Int], Long)] = @@ -574,21 +562,6 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { membersUpdateNew ) - // push service message to user, who invited leaving user - optInviter = state.members.get(cmd.userId) map (_.inviterUserId) - _ ← optInviter map { inviter ⇒ - seqUpdExt.deliverUserUpdate( - userId = cmd.userId, - update = serviceMessageUpdate( - cmd.userId, - dateMillis, - cmd.randomId, - serviceMessage - ), - deliveryTag = Some(Optimization.GroupV2) - ) - } getOrElse FastFuture.successful(()) - // push left user that he is no longer a member SeqState(seq, state) ← seqUpdExt.deliverClientUpdate( userId = cmd.userId, diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/sequence/Optimization.scala b/actor-server/actor-core/src/main/scala/im/actor/server/sequence/Optimization.scala index 6ae868af4f..838c33ae16 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/sequence/Optimization.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/sequence/Optimization.scala @@ -44,7 +44,14 @@ object Optimization extends MessageParsing { UpdateGroupMembersUpdated.header, UpdateGroupMemberDiff.header, UpdateGroupMembersCountChanged.header, - UpdateGroupMemberAdminChanged.header + UpdateGroupMemberAdminChanged.header, + UpdateGroupCanEditInfoChanged.header, + + UpdateGroupShortNameChanged.header, + UpdateGroupCanEditUsernameChanged.header, + UpdateGroupCanEditAdminsChanged.header, + UpdateGroupCanViewAdminsChanged.header, + UpdateGroupCanEditAdminSettingsChanged.header ) if (deliveryTag == GroupV2) emptyUpdate From 2722e6e6444773f96a828ad02fc5641a8d95336d Mon Sep 17 00:00:00 2001 From: rockjam Date: Mon, 18 Jul 2016 22:53:42 +0300 Subject: [PATCH 140/414] fix(server:groups): add group admin settings --- .../actor-core/src/main/protobuf/group.proto | 16 ++ .../src/main/protobuf/groupV2.proto | 41 +++- .../im/actor/server/api/TypeMappers.scala | 16 +- .../im/actor/server/dialog/HistoryUtils.scala | 2 +- .../server/group/GroupCommandHandlers.scala | 179 +++++++++++++++++- .../im/actor/server/group/GroupErrors.scala | 2 + .../actor/server/group/GroupOperations.scala | 18 +- .../actor/server/group/GroupProcessor.scala | 10 +- .../server/group/GroupQueryHandlers.scala | 36 +++- .../im/actor/server/group/GroupState.scala | 141 +++++++++++--- .../actor/server/sequence/Optimization.scala | 3 +- .../actor/server/persist/GroupUserRepo.scala | 4 + .../rpc/service/groups/GroupRpcErrors.scala | 27 +-- .../service/groups/GroupsServiceImpl.scala | 27 ++- 14 files changed, 452 insertions(+), 70 deletions(-) diff --git a/actor-server/actor-core/src/main/protobuf/group.proto b/actor-server/actor-core/src/main/protobuf/group.proto index b4f50d938c..cdfc8d94b4 100644 --- a/actor-server/actor-core/src/main/protobuf/group.proto +++ b/actor-server/actor-core/src/main/protobuf/group.proto @@ -112,6 +112,7 @@ message GroupEvents { optional string topic = 1; } + // deprecated in favour of AdminStatusChanged message UserBecameAdmin { option (scalapb.message).extends = "im.actor.server.group.GroupEvent"; @@ -120,6 +121,14 @@ message GroupEvents { required int32 promoter_user_id = 2; } + message AdminStatusChanged { + option (scalapb.message).extends = "im.actor.server.group.GroupEvent"; + + required int64 ts = 1 [(scalapb.field).type = "java.time.Instant"]; + required int32 user_id = 2; + required bool is_admin = 3; + } + message IntegrationTokenRevoked { option (scalapb.message).extends = "im.actor.server.group.GroupEvent"; @@ -133,4 +142,11 @@ message GroupEvents { required int64 ts = 1 [(scalapb.field).type = "java.time.Instant"]; required int32 user_id = 2; } + + message AdminSettingsUpdated { + option (scalapb.message).extends = "im.actor.server.group.GroupEvent"; + + required int64 ts = 1 [(scalapb.field).type = "java.time.Instant"]; + required int32 settings_bit_mask = 2; + } } diff --git a/actor-server/actor-core/src/main/protobuf/groupV2.proto b/actor-server/actor-core/src/main/protobuf/groupV2.proto index 297dd7f9f5..8ca35e184d 100644 --- a/actor-server/actor-core/src/main/protobuf/groupV2.proto +++ b/actor-server/actor-core/src/main/protobuf/groupV2.proto @@ -34,7 +34,9 @@ message GroupEnvelope { // GroupCommands.MakePublic make_public = 11; GroupCommands.RevokeIntegrationToken revoke_token = 12; GroupCommands.MakeUserAdmin make_user_admin = 13; + GroupCommands.DismissUserAdmin dismiss_user_admin = 28; GroupCommands.TransferOwnership transfer_ownership = 14; + GroupCommands.UpdateAdminSettings update_admin_settings = 30; } oneof query { GroupQueries.GetAccessHash get_access_hash = 15; @@ -48,6 +50,7 @@ message GroupEnvelope { GroupQueries.GetApiFullStruct get_api_full_struct = 23; GroupQueries.CheckAccessHash check_access_hash = 24; GroupQueries.CanSendMessage can_send_message = 26; + GroupQueries.LoadAdminSettings load_admin_settings = 29; } DialogEnvelope dialog_envelope = 25; } @@ -125,16 +128,7 @@ message GroupCommands { string title = 3; int64 random_id = 4; } -// -// message MakePublic { -// option (scalapb.message).extends = "im.actor.server.group.GroupCommand"; -// -// required int32 group_id = 1; -// optional string descrption = 2; -// } -// -// message MakePublicAck {} -// + message UpdateTopic { option (scalapb.message).extends = "GroupCommand"; @@ -169,6 +163,14 @@ message GroupCommands { int32 candidate_user_id = 3; } + message DismissUserAdmin { + option (scalapb.message).extends = "GroupCommand"; + + int32 client_user_id = 1; + int64 client_auth_id = 2; + int32 target_user_id = 3; + } + message RevokeIntegrationToken { option (scalapb.message).extends = "GroupCommand"; @@ -186,6 +188,15 @@ message GroupCommands { int64 client_auth_id = 3; int32 new_owner_id = 4; } + + message UpdateAdminSettings { + option (scalapb.message).extends = "GroupCommand"; + + int32 client_user_id = 1; + int32 settings_bit_mask = 2; + } + + message UpdateAdminSettingsAck {} } message GroupQueries { @@ -296,4 +307,14 @@ message GroupQueries { repeated int32 member_ids = 3; google.protobuf.Int32Value bot_id = 4; } + + message LoadAdminSettings { + option (scalapb.message).extends = "GroupQuery"; + + int32 client_user_id = 1; + } + + message LoadAdminSettingsResponse { + bytes settings = 1 [(scalapb.field).type = "im.actor.api.rpc.groups.ApiAdminSettings"]; + } } diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/api/TypeMappers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/api/TypeMappers.scala index 1c266761fd..b7ab276224 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/api/TypeMappers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/api/TypeMappers.scala @@ -9,7 +9,7 @@ import com.google.protobuf.wrappers.{ BytesValue, Int64Value } import com.google.protobuf.{ ByteString, CodedInputStream } import com.trueaccord.scalapb.TypeMapper import im.actor.api.rpc.files.ApiAvatar -import im.actor.api.rpc.groups.{ ApiGroup, ApiGroupFull } +import im.actor.api.rpc.groups.{ ApiAdminSettings, ApiGroup, ApiGroupFull } import im.actor.api.rpc.messaging.ApiMessage import im.actor.api.rpc.misc.ApiExtension import im.actor.api.rpc.peers.ApiPeer @@ -184,6 +184,18 @@ private[api] trait MessageMapper { def unapplyExtension(ext: ApiExtension): ByteString = ByteString.copyFrom(ext.toByteArray) + private def applyAdminSettings(bytes: ByteString): ApiAdminSettings = { + if (bytes.size() > 0) { + val res = ApiAdminSettings.parseFrom(CodedInputStream.newInstance(bytes.toByteArray)) + get(res) + } else { + null + } + } + + private def unapplyAdminSettings(settings: ApiAdminSettings): ByteString = + ByteString.copyFrom(settings.toByteArray) + implicit val seqUpdMapper: TypeMapper[ByteString, SeqUpdate] = TypeMapper(applySeqUpdate)(unapplySeqUpdate) implicit val anyRefMapper: TypeMapper[ByteString, AnyRef] = TypeMapper(applyAnyRef)(unapplyAnyRef) @@ -216,6 +228,8 @@ private[api] trait MessageMapper { implicit val extensionMapper: TypeMapper[ByteString, ApiExtension] = TypeMapper(applyExtension)(unapplyExtension) + implicit val adminSettingsMapper: TypeMapper[ByteString, ApiAdminSettings] = TypeMapper(applyAdminSettings)(unapplyAdminSettings) + implicit def actorRefMapper(implicit system: ActorSystem): TypeMapper[String, ActorRef] = new ActorSystemMapper[String, ActorRef]() { override def toCustom(base: String): ActorRef = diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/dialog/HistoryUtils.scala b/actor-server/actor-core/src/main/scala/im/actor/server/dialog/HistoryUtils.scala index 7a67ff99d9..047631dfee 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/dialog/HistoryUtils.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/dialog/HistoryUtils.scala @@ -131,6 +131,6 @@ object HistoryUtils { private def requirePrivatePeer(peer: Peer) = { if (peer.typ != PeerType.Private) - throw new Exception("peer should be Private") + throw new RuntimeException("sender should be Private peer") } } diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala index 16eb72e539..71bd080d13 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala @@ -27,7 +27,7 @@ import im.actor.util.misc.{ IdUtils, StringUtils } import scala.concurrent.Future -//TODO: spit into MemberCommandHandlers - InfoCommandHandlers - ControlCommandHandlers +//TODO: spit into MemberCommandHandlers - InfoCommandHandlers - AdminCommandHandlers private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { this: GroupProcessor ⇒ @@ -488,11 +488,23 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { val updateObsolete = UpdateGroupUserLeaveObsolete(groupId, cmd.userId, dateMillis, cmd.randomId) + // TODO: merge, they are almost identical val leftUserUpdatesNew = if (state.typ.isChannel) List( + UpdateGroupCanViewMembersChanged(groupId, canViewMembers = false), + UpdateGroupCanEditInfoChanged(groupId, canEditGroup = false), + UpdateGroupCanEditUsernameChanged(groupId, canEditUsername = false), + UpdateGroupCanEditAdminsChanged(groupId, canAssignAdmins = false), + UpdateGroupCanViewAdminsChanged(groupId, canViewAdmins = false), + UpdateGroupCanEditAdminSettingsChanged(groupId, canEditAdminSettings = false), UpdateGroupCanInviteMembersChanged(groupId, canInviteMembers = false) ) else List( + UpdateGroupCanEditInfoChanged(groupId, canEditGroup = false), + UpdateGroupCanEditUsernameChanged(groupId, canEditUsername = false), + UpdateGroupCanEditAdminsChanged(groupId, canAssignAdmins = false), + UpdateGroupCanViewAdminsChanged(groupId, canViewAdmins = false), + UpdateGroupCanEditAdminSettingsChanged(groupId, canEditAdminSettings = false), UpdateGroupCanViewMembersChanged(groupId, canViewMembers = false), UpdateGroupMembersUpdated(groupId, members = Vector.empty), UpdateGroupCanInviteMembersChanged(groupId, canInviteMembers = false) @@ -617,15 +629,27 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { val updateObsolete = UpdateGroupUserKickObsolete(groupId, cmd.kickedUserId, cmd.kickerUserId, dateMillis, cmd.randomId) + // TODO: merge, they are almost identical val kickedUserUpdatesNew: List[Update] = if (state.typ.isChannel) List( + UpdateGroupCanViewMembersChanged(groupId, canViewMembers = false), + UpdateGroupCanEditInfoChanged(groupId, canEditGroup = false), + UpdateGroupCanEditUsernameChanged(groupId, canEditUsername = false), + UpdateGroupCanEditAdminsChanged(groupId, canAssignAdmins = false), + UpdateGroupCanViewAdminsChanged(groupId, canViewAdmins = false), + UpdateGroupCanEditAdminSettingsChanged(groupId, canEditAdminSettings = false), UpdateGroupCanInviteMembersChanged(groupId, canInviteMembers = false), UpdateGroupMemberChanged(groupId, isMember = false) ) else List( UpdateGroupCanViewMembersChanged(groupId, canViewMembers = false), - UpdateGroupMembersUpdated(groupId, members = Vector.empty), + UpdateGroupCanEditInfoChanged(groupId, canEditGroup = false), + UpdateGroupCanEditUsernameChanged(groupId, canEditUsername = false), + UpdateGroupCanEditAdminsChanged(groupId, canAssignAdmins = false), + UpdateGroupCanViewAdminsChanged(groupId, canViewAdmins = false), + UpdateGroupCanEditAdminSettingsChanged(groupId, canEditAdminSettings = false), UpdateGroupCanInviteMembersChanged(groupId, canInviteMembers = false), + UpdateGroupMembersUpdated(groupId, members = Vector.empty), UpdateGroupMemberChanged(groupId, isMember = false) ) @@ -1066,7 +1090,7 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { } else if (state.isAdmin(cmd.candidateUserId)) { sender() ! Status.Failure(UserAlreadyAdmin) } else { - persist(UserBecameAdmin(Instant.now, cmd.candidateUserId, cmd.clientUserId)) { evt ⇒ + persist(AdminStatusChanged(Instant.now, cmd.candidateUserId, isAdmin = true)) { evt ⇒ val newState = commit(evt) val dateMillis = evt.ts.toEpochMilli @@ -1075,6 +1099,8 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { val updateAdmin = UpdateGroupMemberAdminChanged(groupId, cmd.candidateUserId, isAdmin = true) val updateMembers = UpdateGroupMembersUpdated(groupId, members) + // now this user is admin, change edit rules for admins + val updateCanEdit = UpdateGroupCanEditInfoChanged(groupId, canEditGroup = newState.adminSettings.canAdminsEditGroupInfo) val updateObsolete = UpdateGroupMembersUpdateObsolete(groupId, members) @@ -1127,7 +1153,10 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { /////////////////////////// // Groups V2 API updates // /////////////////////////// - + _ ← seqUpdExt.deliverUserUpdate( + userId = cmd.candidateUserId, + update = updateCanEdit + ) seqStateDate ← if (state.typ.isChannel) adminCHANNELUpdates else adminGROUPUpdates } yield (members, seqStateDate) @@ -1137,6 +1166,90 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { } } + protected def dismissUserAdmin(cmd: DismissUserAdmin): Unit = { + if (!state.permissions.canEditAdmins(cmd.clientUserId)) { + sender() ! Status.Failure(NotAdmin) // Forbidden + } else if (!state.isAdmin(cmd.targetUserId)) { + sender() ! Status.Failure(UserAlreadyNotAdmin) + } else { + persist(AdminStatusChanged(Instant.now, cmd.targetUserId, isAdmin = false)) { evt ⇒ + val newState = commit(evt) + + val dateMillis = evt.ts.toEpochMilli + val memberIds = newState.memberIds + val members = newState.members.values.map(_.asStruct).toVector + + val updateAdmin = UpdateGroupMemberAdminChanged(groupId, cmd.targetUserId, isAdmin = false) + val updateMembers = UpdateGroupMembersUpdated(groupId, members) + // now this user is not admin, change edit rules to plain members + val updateCanEdit = UpdateGroupCanEditInfoChanged(groupId, canEditGroup = newState.adminSettings.canMembersEditGroupInfo) + + val updateObsolete = UpdateGroupMembersUpdateObsolete(groupId, members) + + //TODO: remove deprecated + db.run(GroupUserRepo.dismissAdmin(groupId, cmd.targetUserId)) + + val adminGROUPUpdates: Future[SeqState] = + for { + // push admin changed to all + _ ← seqUpdExt.broadcastPeopleUpdate( + userIds = memberIds + cmd.clientUserId, + updateAdmin + ) + // push changed members to all users + seqState ← seqUpdExt.broadcastClientUpdate( + cmd.clientUserId, + cmd.clientAuthId, + memberIds - cmd.clientUserId, + updateMembers + ) + } yield seqState + + val adminCHANNELUpdates: Future[SeqState] = + for { + // push admin changed to all + _ ← seqUpdExt.broadcastPeopleUpdate( + userIds = memberIds + cmd.clientUserId, + updateAdmin + ) + // push changed members to admins and fresh admin + seqState ← seqUpdExt.broadcastClientUpdate( + cmd.clientUserId, + cmd.clientAuthId, + newState.adminIds - cmd.clientUserId, + updateMembers + ) + } yield seqState + + val result: Future[SeqState] = for { + + /////////////////////////// + // Groups V1 API updates // + /////////////////////////// + + _ ← seqUpdExt.broadcastClientUpdate( + cmd.clientUserId, + cmd.clientAuthId, + memberIds - cmd.clientUserId, + updateObsolete + ) + + /////////////////////////// + // Groups V2 API updates // + /////////////////////////// + _ ← seqUpdExt.deliverUserUpdate( + userId = cmd.targetUserId, + update = updateCanEdit + ) + seqState ← if (state.typ.isChannel) adminCHANNELUpdates else adminGROUPUpdates + + } yield seqState + + result pipeTo sender() + } + } + } + protected def transferOwnership(cmd: TransferOwnership): Unit = { if (!state.isOwner(cmd.clientUserId)) { sender() ! Status.Failure(CommonErrors.Forbidden) @@ -1160,6 +1273,62 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { } } + protected def updateAdminSettings(cmd: UpdateAdminSettings): Unit = { + if (!state.permissions.canEditAdminSettings(cmd.clientUserId)) { + sender() ! Status.Failure(NotAdmin) + } else { + val settOld = state.adminSettings + + persist(AdminSettingsUpdated(Instant.now, cmd.settingsBitMask)) { evt ⇒ + val newState = commit(evt) + val settNew = newState.adminSettings + + val (membersUpdates, adminsUpdates) = { + // push to all members except admins and owner + val showAdminToMembers = PartialFunction.condOpt(settOld.showAdminsToMembers != settNew.showAdminsToMembers) { + case true ⇒ UpdateGroupCanViewAdminsChanged(groupId, canViewAdmins = settNew.showAdminsToMembers) + } + + // push to all members except admins and owner + val canMembersInvite = PartialFunction.condOpt(settOld.canMembersInvite != settNew.canMembersInvite) { + case true ⇒ UpdateGroupCanInviteMembersChanged(groupId, canInviteMembers = settNew.canMembersInvite) + } + + // push to all members except admins and owner + val canMembersEditGroupInfo = PartialFunction.condOpt(settOld.canMembersEditGroupInfo != settNew.canMembersEditGroupInfo) { + case true ⇒ UpdateGroupCanEditInfoChanged(groupId, canEditGroup = settNew.canMembersEditGroupInfo) + } + + // push to admins only + val canAdminsEditGroupInfo = PartialFunction.condOpt(settOld.canAdminsEditGroupInfo != settNew.canAdminsEditGroupInfo) { + case true ⇒ UpdateGroupCanEditInfoChanged(groupId, canEditGroup = settNew.canAdminsEditGroupInfo) + } + + ( + List(showAdminToMembers, canMembersInvite, canMembersEditGroupInfo).flatten[Update], + List(canAdminsEditGroupInfo).flatten[Update] + ) + } + + val plainMemberIds = (newState.memberIds - newState.ownerUserId) -- newState.adminIds + val adminsOnlyIds = newState.adminIds - newState.ownerUserId + + val result: Future[UpdateAdminSettingsAck] = for { + // deliver updates about settings changed to plain group members + _ ← FutureExt.ftraverse(membersUpdates) { update ⇒ + seqUpdExt.broadcastPeopleUpdate(userIds = plainMemberIds, update) + } + // deliver updates about settings changed to plain group members + _ ← FutureExt.ftraverse(membersUpdates) { update ⇒ + seqUpdExt.broadcastPeopleUpdate(userIds = adminsOnlyIds, update) + } + } yield UpdateAdminSettingsAck() + + result pipeTo sender() + } + } + } + private def serviceMessageUpdate(senderUserId: Int, date: Long, randomId: Long, message: ApiServiceMessage) = UpdateMessage( peer = apiGroupPeer, @@ -1191,7 +1360,7 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { UpdateGroupTopicChanged(groupId, newState.topic), UpdateGroupTitleChanged(groupId, newState.title), UpdateGroupOwnerChanged(groupId, newState.ownerUserId), - UpdateGroupCanViewMembersChanged(groupId, canViewMembers = newState.canViewMembers(userId)), + UpdateGroupCanViewMembersChanged(groupId, canViewMembers = newState.permissions.canViewMembers(userId)), UpdateGroupCanInviteMembersChanged(groupId, canInviteMembers = true) // TODO: figure out right value // UpdateGroupExtChanged(groupId, newState.extension) //TODO: figure out and fix // if(bigGroup) UpdateGroupMembersCountChanged(groupId, newState.extension) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupErrors.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupErrors.scala index a27657da21..28f2848875 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupErrors.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupErrors.scala @@ -17,6 +17,8 @@ object GroupErrors { case object UserAlreadyAdmin extends Exception with NoStackTrace + case object UserAlreadyNotAdmin extends Exception with NoStackTrace + case object NotAdmin extends Exception with NoStackTrace case object NotOwner extends Exception with NoStackTrace diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupOperations.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupOperations.scala index 5ceef50cd9..6239b0b4f6 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupOperations.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupOperations.scala @@ -5,7 +5,7 @@ import akka.pattern.ask import akka.util.Timeout import com.google.protobuf.ByteString import im.actor.api.rpc.AuthorizedClientData -import im.actor.api.rpc.groups.{ ApiGroup, ApiGroupFull, ApiMember } +import im.actor.api.rpc.groups.{ ApiAdminSettings, ApiGroup, ApiGroupFull, ApiMember } import im.actor.server.dialog.UserAcl import im.actor.server.file.Avatar import im.actor.server.sequence.{ SeqState, SeqStateDate } @@ -97,6 +97,11 @@ private[group] sealed trait Commands extends UserAcl { GroupEnvelope(groupId) .withMakeUserAdmin(MakeUserAdmin(clientUserId, clientAuthId, candidateId))).mapTo[(Vector[ApiMember], SeqStateDate)] + def dismissUserAdmin(groupId: Int, clientUserId: Int, clientAuthId: Long, targetUserId: Int): Future[SeqState] = + (processorRegion.ref ? + GroupEnvelope(groupId) + .withDismissUserAdmin(DismissUserAdmin(clientUserId, clientAuthId, targetUserId))).mapTo[SeqState] + def revokeIntegrationToken(groupId: Int, clientUserId: Int): Future[String] = (processorRegion.ref ? GroupEnvelope(groupId) @@ -107,6 +112,11 @@ private[group] sealed trait Commands extends UserAcl { GroupEnvelope(groupId) .withTransferOwnership(TransferOwnership(clientUserId, clientAuthId, newOwnerId))).mapTo[SeqState] + def updateAdminSettings(groupId: Int, clientUserId: Int, settings: ApiAdminSettings): Future[Unit] = + (processorRegion.ref ? + GroupEnvelope(groupId) + .withUpdateAdminSettings(UpdateAdminSettings(clientUserId, AdminSettings.apiToBitMask(settings)))).mapTo[UpdateAdminSettingsAck] map (_ ⇒ ()) + } private[group] sealed trait Queries { @@ -187,6 +197,12 @@ private[group] sealed trait Queries { (viewRegion.ref ? GroupEnvelope(groupId) .withLoadMembers(LoadMembers(clientUserId, limit, offset map ByteString.copyFrom))).mapTo[LoadMembersResponse] map (r ⇒ r.userIds → r.offset.map(_.toByteArray)) + + def loadAdminSettings(groupId: Int, clientUserId: Int): Future[ApiAdminSettings] = { + (viewRegion.ref ? + GroupEnvelope(groupId) + .withLoadAdminSettings(LoadAdminSettings(clientUserId))).mapTo[LoadAdminSettingsResponse] map (_.settings) + } } final case class CanSendMessageInfo( diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala index a37f813d9d..dbf2f948a3 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala @@ -47,6 +47,8 @@ object GroupProcessor { 20020 → classOf[GroupCommands.RevokeIntegrationTokenAck], 20021 → classOf[GroupCommands.TransferOwnership], 20022 → classOf[GroupCommands.UpdateShortName], + 20023 → classOf[GroupCommands.DismissUserAdmin], + 20024 → classOf[GroupCommands.UpdateAdminSettings], 21001 → classOf[GroupQueries.GetIntegrationToken], 21002 → classOf[GroupQueries.GetIntegrationTokenResponse], @@ -67,6 +69,8 @@ object GroupProcessor { 21018 → classOf[GroupQueries.GetApiFullStruct], 21019 → classOf[GroupQueries.CanSendMessage], 21020 → classOf[GroupQueries.CanSendMessageResponse], + 21021 → classOf[GroupQueries.LoadAdminSettings], + 21022 → classOf[GroupQueries.LoadAdminSettingsResponse], 22003 → classOf[GroupEvents.UserInvited], 22004 → classOf[GroupEvents.UserJoined], @@ -82,7 +86,8 @@ object GroupProcessor { 22015 → classOf[GroupEvents.UserBecameAdmin], 22016 → classOf[GroupEvents.IntegrationTokenRevoked], 22017 → classOf[GroupEvents.OwnerChanged], - 22018 → classOf[GroupEvents.ShortNameUpdated] + 22018 → classOf[GroupEvents.ShortNameUpdated], + 22019 → classOf[GroupEvents.AdminSettingsUpdated] ) def persistenceIdFor(groupId: Int): String = s"Group-${groupId}" @@ -134,7 +139,9 @@ private[group] final class GroupProcessor // admin actions case r: RevokeIntegrationToken ⇒ revokeIntegrationToken(r) case m: MakeUserAdmin ⇒ makeUserAdmin(m) + case d: DismissUserAdmin ⇒ dismissUserAdmin(d) case t: TransferOwnership ⇒ transferOwnership(t) + case s: UpdateAdminSettings ⇒ updateAdminSettings(s) // termination actions case StopProcessor ⇒ context stop self @@ -165,6 +172,7 @@ private[group] final class GroupProcessor case GetApiFullStruct(clientUserId) ⇒ getApiFullStruct(clientUserId) case CheckAccessHash(accessHash) ⇒ checkAccessHash(accessHash) case CanSendMessage(clientUserId) ⇒ canSendMessage(clientUserId) + case LoadAdminSettings(clientUserId) ⇒ loadAdminSettings(clientUserId) } def persistenceId: String = GroupProcessor.persistenceIdFor(groupId) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala index 29bf454304..0f13f3694e 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala @@ -5,7 +5,8 @@ import akka.stream.ActorMaterializer import akka.stream.scaladsl.Source import com.google.protobuf.ByteString import com.google.protobuf.wrappers.Int32Value -import im.actor.api.rpc.groups.{ ApiGroup, ApiGroupFull, ApiGroupType, ApiMember } +import im.actor.api.rpc.groups._ +import im.actor.server.group.GroupErrors.NotOwner import im.actor.server.group.GroupQueries._ import im.actor.server.group.GroupType.{ Channel, General, Public } @@ -102,7 +103,7 @@ trait GroupQueryHandlers { case GroupType.Channel ⇒ ApiGroupType.CHANNEL case GroupType.General | GroupType.Public | GroupType.Unrecognized(_) ⇒ ApiGroupType.GROUP }), - canSendMessage = Some(state.canSendMessage(clientUserId)) + canSendMessage = Some(state.permissions.canSendMessage(clientUserId)) ) ) } @@ -119,17 +120,17 @@ trait GroupQueryHandlers { ownerUserId = state.getShowableOwner(clientUserId), createDate = extractCreatedAtMillis(state), ext = None, - canViewMembers = Some(state.canViewMembers(clientUserId)), - canInvitePeople = Some(state.canInvitePeople(clientUserId)), + canViewMembers = Some(state.permissions.canViewMembers(clientUserId)), + canInvitePeople = Some(state.permissions.canInvitePeople(clientUserId)), isSharedHistory = Some(state.isHistoryShared), isAsyncMembers = Some(state.isAsyncMembers), members = membersAndCount(state, clientUserId)._1, shortName = state.shortName, - canEditGroupInfo = None, - canEditShortName = None, - canEditAdminList = None, - canViewAdminList = None, - canEditAdminSettings = None + canEditGroupInfo = Some(state.permissions.canEditInfo(clientUserId)), + canEditShortName = Some(state.permissions.canEditShortName(clientUserId)), + canEditAdminList = Some(state.permissions.canEditAdmins(clientUserId)), + canViewAdminList = Some(state.permissions.canViewAdmins(clientUserId)), + canEditAdminSettings = Some(state.permissions.canEditAdminSettings(clientUserId)) ) ) } @@ -153,6 +154,23 @@ trait GroupQueryHandlers { ) } + protected def loadAdminSettings(clientUserId: Int): Future[LoadAdminSettingsResponse] = { + if (state.permissions.canEditAdminSettings(clientUserId)) { + FastFuture.successful { + LoadAdminSettingsResponse( + ApiAdminSettings( + showAdminsToMembers = state.adminSettings.showAdminsToMembers, + canMembersInvite = state.adminSettings.canMembersInvite, + canMembersEditGroupInfo = state.adminSettings.canMembersEditGroupInfo, + canAdminsEditGroupInfo = state.adminSettings.canAdminsEditGroupInfo + ) + ) + } + } else { + FastFuture.failed(NotOwner) + } + } + private def extractCreatedAtMillis(group: GroupState): Long = group.createdAt.map(_.toEpochMilli).getOrElse(throw new RuntimeException("No date created provided for group!")) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala index 7e5d0a4d31..aff4d25f1c 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala @@ -3,6 +3,7 @@ package im.actor.server.group import java.time.Instant import akka.persistence.SnapshotMetadata +import im.actor.api.rpc.groups.ApiAdminSettings import im.actor.api.rpc.misc.ApiExtension import im.actor.server.cqrs.{ Event, ProcessorState } import im.actor.server.file.Avatar @@ -13,7 +14,7 @@ private[group] final case class Member( userId: Int, inviterUserId: Int, invitedAt: Instant, - isAdmin: Boolean + isAdmin: Boolean // TODO: remove, use separate admins list instead ) private[group] final case class Bot( @@ -21,6 +22,44 @@ private[group] final case class Bot( token: String ) +object AdminSettings { + val Default = AdminSettings( + showAdminsToMembers = true, + canMembersInvite = true, + canMembersEditGroupInfo = true, + canAdminsEditGroupInfo = false + ) + + // format: OFF + def apiToBitMask(settings: ApiAdminSettings): Int = { + def toInt(b: Boolean) = if(b) 1 else 0 + + List( + toInt(settings.showAdminsToMembers) << 0, + toInt(settings.canMembersInvite) << 1, + toInt(settings.canMembersEditGroupInfo) << 2, + toInt(settings.canAdminsEditGroupInfo) << 3 + ).sum + } + + def fromBitMask(mask: Int): AdminSettings = { + AdminSettings( + showAdminsToMembers = (mask & (1 << 0)) != 0, + canMembersInvite = (mask & (1 << 1)) != 0, + canMembersEditGroupInfo = (mask & (1 << 2)) != 0, + canAdminsEditGroupInfo = (mask & (1 << 3)) != 0 + ) + } + // format: ON +} + +private[group] final case class AdminSettings( + showAdminsToMembers: Boolean, // 1 + canMembersInvite: Boolean, // 2 + canMembersEditGroupInfo: Boolean, // 4 + canAdminsEditGroupInfo: Boolean // 8 +) + private[group] object GroupState { def empty: GroupState = GroupState( @@ -40,6 +79,7 @@ private[group] object GroupState { members = Map.empty, invitedUserIds = Set.empty, accessHash = 0L, + adminSettings = AdminSettings.Default, bot = None, //??? @@ -70,9 +110,10 @@ private[group] final case class GroupState( invitedUserIds: Set[Int], //security and etc. - accessHash: Long, - bot: Option[Bot], - extensions: Map[Int, Array[Byte]] + accessHash: Long, + adminSettings: AdminSettings, + bot: Option[Bot], + extensions: Map[Int, Array[Byte]] ) extends ProcessorState[GroupState] { lazy val memberIds = members.keySet @@ -96,24 +137,6 @@ private[group] final case class GroupState( def isExUser(userId: Int): Boolean = exUserIds.contains(userId) - // in case of general/public can view members if user is member - // in case of channel can view members only if clientUserId is admin - def canViewMembers(clientUserId: Int) = - isMember(clientUserId) && ((typ.isGeneral || typ.isPublic) || (typ.isChannel && isAdmin(clientUserId))) - - /** - * For now, all members can invite other users to group - */ - def canInvitePeople(clientUserId: Int) = isMember(clientUserId) - - def canSendMessage(clientUserId: Int) = - { - typ match { - case General | Public ⇒ isMember(clientUserId) - case Channel ⇒ isAdmin(clientUserId) - } - } || bot.exists(_.userId == clientUserId) - val isNotCreated = createdAt.isEmpty val isCreated = createdAt.nonEmpty @@ -216,9 +239,9 @@ private[group] final case class GroupState( this.copy(about = newAbout) case TopicUpdated(_, newTopic) ⇒ this.copy(topic = newTopic) - case UserBecameAdmin(_, userId, _) ⇒ + case AdminStatusChanged(_, userId, isAdmin) ⇒ this.copy( - members = members.updated(userId, members(userId).copy(isAdmin = true)) + members = members.updated(userId, members(userId).copy(isAdmin = isAdmin)) ) case IntegrationTokenRevoked(_, newToken) ⇒ this.copy( @@ -228,8 +251,78 @@ private[group] final case class GroupState( this.copy(ownerUserId = userId) case ShortNameUpdated(_, newShortName) ⇒ this.copy(shortName = newShortName) + case AdminSettingsUpdated(_, bitMask) ⇒ + this.copy(adminSettings = AdminSettings.fromBitMask(bitMask)) + + // deprecated event + case UserBecameAdmin(_, userId, _) ⇒ + this.copy( + members = members.updated(userId, members(userId).copy(isAdmin = true)) + ) } // TODO: real snapshot def withSnapshot(metadata: SnapshotMetadata, snapshot: Any): GroupState = this + + object permissions { + + /** + * bot can send messages in all groups + * in general/public group only members can send messages + * in channels only owner and admins can send messages + */ + def canSendMessage(clientUserId: Int) = + { + typ match { + case General | Public ⇒ isMember(clientUserId) + case Channel ⇒ isAdmin(clientUserId) || isOwner(clientUserId) + } + } || bot.exists(_.userId == clientUserId) + + /** + * in general/public group, all members can view members + * in channels, owner and admins can view members + */ + def canViewMembers(clientUserId: Int) = + typ match { + case General | Public ⇒ isMember(clientUserId) + case Channel ⇒ isAdmin(clientUserId) || isOwner(clientUserId) + } + + /** + * owner and admins always can invite new people + * members can invite new people if canMembersInvite is true + */ + def canInvitePeople(clientUserId: Int) = + isOwner(clientUserId) || + isAdmin(clientUserId) || + (isMember(clientUserId) && adminSettings.canMembersInvite) + + /** + * owner always can edit group info + * admin can edit group info, if canAdminsEditGroupInfo is true in admin settings + * any member can edit group info, if canMembersEditGroupInfo is true in admin settings + */ + def canEditInfo(clientUserId: Int): Boolean = + isOwner(clientUserId) || + (isAdmin(clientUserId) && adminSettings.canAdminsEditGroupInfo) || + (isMember(clientUserId) && adminSettings.canMembersEditGroupInfo) + + // only owner can change short name + def canEditShortName(clientUserId: Int): Boolean = isOwner(clientUserId) + + // only owner and other admins can edit admins list + def canEditAdmins(clientUserId: Int): Boolean = + isOwner(clientUserId) || isAdmin(clientUserId) + + /** + * admins list is always visible to owner and admins + * admins list is visible to any member if showAdminsToMembers = true + */ + def canViewAdmins(clientUserId: Int): Boolean = + isOwner(clientUserId) || isAdmin(clientUserId) || adminSettings.showAdminsToMembers + + // only owner can change admin settings + def canEditAdminSettings(clientUserId: Int): Boolean = isOwner(clientUserId) + } } diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/sequence/Optimization.scala b/actor-server/actor-core/src/main/scala/im/actor/server/sequence/Optimization.scala index 838c33ae16..7f517876c4 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/sequence/Optimization.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/sequence/Optimization.scala @@ -45,9 +45,8 @@ object Optimization extends MessageParsing { UpdateGroupMemberDiff.header, UpdateGroupMembersCountChanged.header, UpdateGroupMemberAdminChanged.header, - UpdateGroupCanEditInfoChanged.header, - UpdateGroupShortNameChanged.header, + UpdateGroupCanEditInfoChanged.header, UpdateGroupCanEditUsernameChanged.header, UpdateGroupCanEditAdminsChanged.header, UpdateGroupCanViewAdminsChanged.header, diff --git a/actor-server/actor-persist/src/main/scala/im/actor/server/persist/GroupUserRepo.scala b/actor-server/actor-persist/src/main/scala/im/actor/server/persist/GroupUserRepo.scala index 8953c2cc1c..f195941789 100644 --- a/actor-server/actor-persist/src/main/scala/im/actor/server/persist/GroupUserRepo.scala +++ b/actor-server/actor-persist/src/main/scala/im/actor/server/persist/GroupUserRepo.scala @@ -72,4 +72,8 @@ object GroupUserRepo { def makeAdmin(groupId: Int, userId: Int) = byPKC.applied((groupId, userId)).map(_.isAdmin).update(true) + @deprecated("Duplication of event-sourced groups logic", "2016-06-05") + def dismissAdmin(groupId: Int, userId: Int) = + byPKC.applied((groupId, userId)).map(_.isAdmin).update(false) + } diff --git a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupRpcErrors.scala b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupRpcErrors.scala index 5949a8883a..ab08be3d57 100644 --- a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupRpcErrors.scala +++ b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupRpcErrors.scala @@ -4,19 +4,20 @@ import im.actor.api.rpc.RpcError // format: OFF object GroupRpcErrors { - val AlreadyInvited = RpcError(400, "USER_ALREADY_INVITED", "You are already invited to this group.", false, None) - val AlreadyJoined = RpcError(400, "USER_ALREADY_JOINED", "You are already a member of this group.", false, None) - val NotAMember = RpcError(403, "FORBIDDEN", "You are not a group member.", false, None) - val InvalidTitle = RpcError(400, "GROUP_TITLE_INVALID", "Invalid group title.", false, None) - val TopicTooLong = RpcError(400, "GROUP_TOPIC_TOO_LONG", "Group topic is too long. It should be no longer then 255 characters", false, None) - val AboutTooLong = RpcError(400, "GROUP_ABOUT_TOO_LONG", "Group about is too long. It should be no longer then 255 characters", false, None) - val UserAlreadyAdmin = RpcError(400, "USER_ALREADY_ADMIN", "User is already admin of this group", false, None) - val BlockedByUser = RpcError(403, "BLOCKED_BY_USER", "User blocked you, unable to invite him.", false, None) - val GroupIdAlreadyExists = RpcError(400, "GROUP_ALREADY_EXISTS", "Group with such id already exists.", false, None) - val InvalidInviteUrl = RpcError(403, "INVALID_INVITE_URL", "Invalid invite url!", false, None) - val InvalidInviteToken = RpcError(403, "INVALID_INVITE_TOKEN", "Invalid invite token!", false, None) - val InvalidInviteGroup = RpcError(403, "INVALID_INVITE_GROUP", "Invalid group name provided!", false, None) - val GroupNotPublic = RpcError(400, "GROUP_IS_NOT_PUBLIC", "The group is not public.", false, None) + val AlreadyInvited = RpcError(400, "USER_ALREADY_INVITED", "You are already invited to this group.", false, None) + val AlreadyJoined = RpcError(400, "USER_ALREADY_JOINED", "You are already a member of this group.", false, None) + val NotAMember = RpcError(403, "FORBIDDEN", "You are not a group member.", false, None) + val InvalidTitle = RpcError(400, "GROUP_TITLE_INVALID", "Invalid group title.", false, None) + val TopicTooLong = RpcError(400, "GROUP_TOPIC_TOO_LONG", "Group topic is too long. It should be no longer then 255 characters", false, None) + val AboutTooLong = RpcError(400, "GROUP_ABOUT_TOO_LONG", "Group about is too long. It should be no longer then 255 characters", false, None) + val UserAlreadyAdmin = RpcError(400, "USER_ALREADY_ADMIN", "User is already admin of this group", false, None) + val UserAlreadyNotAdmin = RpcError(400, "USER_ALREADY_NOT_ADMIN", "User is already notadmin of this group", false, None) + val BlockedByUser = RpcError(403, "BLOCKED_BY_USER", "User blocked you, unable to invite him.", false, None) + val GroupIdAlreadyExists = RpcError(400, "GROUP_ALREADY_EXISTS", "Group with such id already exists.", false, None) + val InvalidInviteUrl = RpcError(403, "INVALID_INVITE_URL", "Invalid invite url!", false, None) + val InvalidInviteToken = RpcError(403, "INVALID_INVITE_TOKEN", "Invalid invite token!", false, None) + val InvalidInviteGroup = RpcError(403, "INVALID_INVITE_GROUP", "Invalid group name provided!", false, None) + val GroupNotPublic = RpcError(400, "GROUP_IS_NOT_PUBLIC", "The group is not public.", false, None) val InvalidShortName = RpcError(400, "GROUP_SHORT_NAME_INVALID", "Invalid group short name. Valid short name should contain from 5 to 32 characters, and may consist of latin characters, numbers and underscores", false, None) val ShortNameTaken = RpcError(400, "GROUP_SHORT_NAME_TAKEN", "This short name already belongs to other user or group, we are sorry!", false, None) diff --git a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupsServiceImpl.scala b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupsServiceImpl.scala index 8b58909a7a..245c3ae591 100644 --- a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupsServiceImpl.scala +++ b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupsServiceImpl.scala @@ -84,13 +84,33 @@ final class GroupsServiceImpl(groupInviteConfig: GroupInviteConfig)(implicit act } override def doHandleDismissUserAdmin(groupPeer: ApiGroupOutPeer, userPeer: ApiUserOutPeer, clientData: ClientData): Future[HandlerResult[ResponseSeq]] = - FastFuture.failed(new RuntimeException("Not implemented")) + authorized(clientData) { implicit client ⇒ + withGroupOutPeer(groupPeer) { + withUserOutPeer(userPeer) { + for { + SeqState(seq, state) ← groupExt.dismissUserAdmin(groupPeer.groupId, client.userId, client.authId, userPeer.userId) + } yield Ok(ResponseSeq(seq, state.toByteArray)) + } + } + } override def doHandleLoadAdminSettings(groupPeer: ApiGroupOutPeer, clientData: ClientData): Future[HandlerResult[ResponseLoadAdminSettings]] = - FastFuture.failed(new RuntimeException("Not implemented")) + authorized(clientData) { client ⇒ + withGroupOutPeer(groupPeer) { + for { + settings ← groupExt.loadAdminSettings(groupPeer.groupId, client.userId) + } yield Ok(ResponseLoadAdminSettings(settings)) + } + } override def doHandleSaveAdminSettings(groupPeer: ApiGroupOutPeer, settings: ApiAdminSettings, clientData: ClientData): Future[HandlerResult[ResponseVoid]] = - FastFuture.failed(new RuntimeException("Not implemented")) + authorized(clientData) { client ⇒ + withGroupOutPeer(groupPeer) { + for { + _ ← groupExt.updateAdminSettings(groupPeer.groupId, client.userId, settings) + } yield Ok(ResponseVoid) + } + } /** * Loading group members @@ -558,6 +578,7 @@ final class GroupsServiceImpl(groupInviteConfig: GroupInviteConfig)(implicit act case GroupErrors.NotAdmin ⇒ CommonRpcErrors.forbidden("Only group admin can perform this action.") case GroupErrors.NotOwner ⇒ CommonRpcErrors.forbidden("Only group owner can perform this action.") case GroupErrors.UserAlreadyAdmin ⇒ GroupRpcErrors.UserAlreadyAdmin + case GroupErrors.UserAlreadyNotAdmin ⇒ GroupRpcErrors.UserAlreadyNotAdmin case GroupErrors.InvalidTitle ⇒ GroupRpcErrors.InvalidTitle case GroupErrors.AboutTooLong ⇒ GroupRpcErrors.AboutTooLong case GroupErrors.TopicTooLong ⇒ GroupRpcErrors.TopicTooLong From cebcb59a2537c65b93170865c1b2598fe5b3fa14 Mon Sep 17 00:00:00 2001 From: rockjam Date: Tue, 19 Jul 2016 01:31:52 +0300 Subject: [PATCH 141/414] fix(server:groups): add permission checks to command handling --- .../server/group/GroupCommandHandlers.scala | 47 ++++++++++--------- .../im/actor/server/group/GroupErrors.scala | 2 + .../im/actor/server/group/GroupState.scala | 6 +++ .../rpc/service/groups/GroupRpcErrors.scala | 1 + .../service/groups/GroupsServiceImpl.scala | 1 + 5 files changed, 34 insertions(+), 23 deletions(-) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala index 71bd080d13..5f7531bb34 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala @@ -35,6 +35,7 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { private val notMember = Status.Failure(NotAMember) private val notAdmin = Status.Failure(NotAdmin) + private val noPermission = Status.Failure(NoPermission) protected def create(cmd: Create): Unit = { if (!isValidTitle(cmd.title)) { @@ -169,7 +170,9 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { } protected def invite(cmd: Invite): Unit = { - if (state.isInvited(cmd.inviteeUserId)) { + if (!state.permissions.canInvitePeople(cmd.inviterUserId)) { + sender() ! noPermission + } else if (state.isInvited(cmd.inviteeUserId)) { sender() ! Status.Failure(GroupErrors.UserAlreadyInvited) } else if (state.isMember(cmd.inviteeUserId)) { sender() ! Status.Failure(GroupErrors.UserAlreadyJoined) @@ -617,9 +620,9 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { } protected def kick(cmd: Kick): Unit = { - if (state.typ.isChannel && !state.isAdmin(cmd.kickedUserId)) { - sender() ! notAdmin - } else if (state.nonMember(cmd.kickerUserId) || state.nonMember(cmd.kickedUserId)) { + if (!state.permissions.canKickMember(cmd.kickerUserId)) { + sender() ! noPermission + } else if (state.nonMember(cmd.kickedUserId)) { sender() ! notMember } else { persist(UserKicked(Instant.now, cmd.kickedUserId, cmd.kickerUserId)) { evt ⇒ @@ -761,10 +764,8 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { } protected def updateAvatar(cmd: UpdateAvatar): Unit = { - if (state.typ.isChannel && !state.isAdmin(cmd.clientUserId)) { - sender() ! notAdmin - } else if (state.nonMember(cmd.clientUserId)) { - sender() ! notMember + if (!state.permissions.canEditInfo(cmd.clientUserId)) { + sender() ! noPermission } else { persist(AvatarUpdated(Instant.now, cmd.avatar)) { evt ⇒ val newState = commit(evt) @@ -812,10 +813,8 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { protected def updateTitle(cmd: UpdateTitle): Unit = { val title = cmd.title - if (state.typ.isChannel && !state.isAdmin(cmd.clientUserId)) { - sender() ! notAdmin - } else if (state.nonMember(cmd.clientUserId)) { - sender() ! notMember + if (!state.permissions.canEditInfo(cmd.clientUserId)) { + sender() ! noPermission } else if (!isValidTitle(title)) { sender() ! Status.Failure(InvalidTitle) } else { @@ -885,10 +884,8 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { val topic = trimToEmpty(cmd.topic) - if (state.typ.isChannel && !state.isAdmin(cmd.clientUserId)) { - sender() ! notAdmin - } else if (state.nonMember(cmd.clientUserId)) { - sender() ! notMember + if (!state.permissions.canEditInfo(cmd.clientUserId)) { + sender() ! noPermission } else if (!isValidTopic(topic)) { sender() ! Status.Failure(TopicTooLong) } else { @@ -957,8 +954,8 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { val about = trimToEmpty(cmd.about) - if (!state.isAdmin(cmd.clientUserId)) { - sender() ! notAdmin + if (!state.permissions.canEditInfo(cmd.clientUserId)) { + sender() ! noPermission } else if (!isValidAbout(about)) { sender() ! Status.Failure(AboutTooLong) } else { @@ -1014,7 +1011,7 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { val oldShortName = state.shortName val newShortName = trimToEmpty(cmd.shortName) - if (!state.isOwner(cmd.clientUserId)) { + if (!state.permissions.canEditShortName(cmd.clientUserId)) { sender() ! Status.Failure(NotOwner) } else if (!isValidShortName(newShortName)) { sender() ! Status.Failure(InvalidShortName) @@ -1059,7 +1056,7 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { } protected def revokeIntegrationToken(cmd: RevokeIntegrationToken): Unit = { - if (!state.isAdmin(cmd.clientUserId)) { + if (!(state.isAdmin(cmd.clientUserId) || state.isOwner(cmd.clientUserId))) { sender() ! notAdmin } else { val oldToken = state.bot.map(_.token) @@ -1085,8 +1082,10 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { } protected def makeUserAdmin(cmd: MakeUserAdmin): Unit = { - if (!state.isAdmin(cmd.clientUserId) || state.nonMember(cmd.candidateUserId)) { - sender() ! Status.Failure(NotAdmin) + if (!state.permissions.canEditAdmins(cmd.clientUserId)) { + sender() ! noPermission + } else if (state.nonMember(cmd.candidateUserId)) { + sender() ! Status.Failure(NotAMember) } else if (state.isAdmin(cmd.candidateUserId)) { sender() ! Status.Failure(UserAlreadyAdmin) } else { @@ -1168,7 +1167,9 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { protected def dismissUserAdmin(cmd: DismissUserAdmin): Unit = { if (!state.permissions.canEditAdmins(cmd.clientUserId)) { - sender() ! Status.Failure(NotAdmin) // Forbidden + sender() ! noPermission + } else if (state.nonMember(cmd.targetUserId)) { + sender() ! Status.Failure(NotAMember) } else if (!state.isAdmin(cmd.targetUserId)) { sender() ! Status.Failure(UserAlreadyNotAdmin) } else { diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupErrors.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupErrors.scala index 28f2848875..06174a95c5 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupErrors.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupErrors.scala @@ -36,4 +36,6 @@ object GroupErrors { case object NoBotFound extends Exception with NoStackTrace case object BlockedByUser extends Exception with NoStackTrace + + case object NoPermission extends Exception with NoStackTrace } diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala index aff4d25f1c..a14cdf8c86 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala @@ -298,6 +298,12 @@ private[group] final case class GroupState( isAdmin(clientUserId) || (isMember(clientUserId) && adminSettings.canMembersInvite) + /** + * owner and admins can kick members + */ + def canKickMember(clientUserId: Int) = + isOwner(clientUserId) || isAdmin(clientUserId) + /** * owner always can edit group info * admin can edit group info, if canAdminsEditGroupInfo is true in admin settings diff --git a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupRpcErrors.scala b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupRpcErrors.scala index ab08be3d57..8505b3ba81 100644 --- a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupRpcErrors.scala +++ b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupRpcErrors.scala @@ -18,6 +18,7 @@ object GroupRpcErrors { val InvalidInviteToken = RpcError(403, "INVALID_INVITE_TOKEN", "Invalid invite token!", false, None) val InvalidInviteGroup = RpcError(403, "INVALID_INVITE_GROUP", "Invalid group name provided!", false, None) val GroupNotPublic = RpcError(400, "GROUP_IS_NOT_PUBLIC", "The group is not public.", false, None) + val NoPermission = RpcError(403, "NO_PERMISSION", "You have no permission to execute this action", false, None) val InvalidShortName = RpcError(400, "GROUP_SHORT_NAME_INVALID", "Invalid group short name. Valid short name should contain from 5 to 32 characters, and may consist of latin characters, numbers and underscores", false, None) val ShortNameTaken = RpcError(400, "GROUP_SHORT_NAME_TAKEN", "This short name already belongs to other user or group, we are sorry!", false, None) diff --git a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupsServiceImpl.scala b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupsServiceImpl.scala index 245c3ae591..1e86f599a5 100644 --- a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupsServiceImpl.scala +++ b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupsServiceImpl.scala @@ -589,6 +589,7 @@ final class GroupsServiceImpl(groupInviteConfig: GroupInviteConfig)(implicit act case GroupErrors.GroupIdAlreadyExists(_) ⇒ GroupRpcErrors.GroupIdAlreadyExists case GroupErrors.InvalidShortName ⇒ GroupRpcErrors.InvalidShortName case GroupErrors.ShortNameTaken ⇒ GroupRpcErrors.ShortNameTaken + case GroupErrors.NoPermission ⇒ GroupRpcErrors.NoPermission } } From ff52354ea5c79934e8fa0c48916125bd8db92149 Mon Sep 17 00:00:00 2001 From: rockjam Date: Tue, 19 Jul 2016 01:40:24 +0300 Subject: [PATCH 142/414] fix(server:groups): rename typ -> groupType in GroupState --- .../server/group/GroupCommandHandlers.scala | 28 +++++++++---------- .../server/group/GroupQueryHandlers.scala | 12 ++++---- .../im/actor/server/group/GroupState.scala | 16 +++++------ 3 files changed, 28 insertions(+), 28 deletions(-) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala index 5f7531bb34..6b8e259e7c 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala @@ -65,7 +65,7 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { // Group creation val groupType = GroupType.fromValue(cmd.typ) //FIXME: make it normal enum - val isHistoryShared = groupType.isChannel + val isHistoryShared = groupType.isChannel || groupType.isPublic persist(Created( ts = createdAt, @@ -101,7 +101,7 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { creatorUserId = newState.creatorUserId, accessHash = newState.accessHash, title = newState.title, - isPublic = newState.typ == GroupType.Public, + isPublic = isHistoryShared, createdAt = evt.ts, about = None, topic = None @@ -190,7 +190,7 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { val inviteeUpdatesNew: List[Update] = refreshGroupUpdates(newState, cmd.inviteeUserId) val membersUpdateNew: Update = - if (newState.typ.isChannel) // if history shared + if (newState.groupType.isChannel) // if channel, or group is big enough UpdateGroupMembersCountChanged(groupId, newState.membersCount) else UpdateGroupMembersUpdated(groupId, apiMembers) @@ -321,7 +321,7 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { // Groups V2 API updates // /////////////////////////// - seqStateDate ← if (newState.typ.isChannel) inviteCHANNELUpdates else inviteGROUPUpdates + seqStateDate ← if (newState.groupType.isChannel) inviteCHANNELUpdates else inviteGROUPUpdates } yield seqStateDate @@ -371,7 +371,7 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { if (wasInvited) List.empty[Update] else refreshGroupUpdates(newState, cmd.joiningUserId) val membersUpdateNew: Update = - if (newState.typ.isChannel) // if history is shared + if (newState.groupType.isChannel) // if channel, or group is big enough UpdateGroupMembersCountChanged(groupId, newState.membersCount) else UpdateGroupMembersUpdated(groupId, apiMembers) // will update date when member got into group @@ -466,7 +466,7 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { // Groups V2 API updates // /////////////////////////// - seqStateDate ← if (newState.typ.isChannel) joinCHANNELUpdates else joinGROUPUpdates + seqStateDate ← if (newState.groupType.isChannel) joinCHANNELUpdates else joinGROUPUpdates } yield (seqStateDate, memberIds.toVector :+ inviterUserId, randomId) @@ -493,7 +493,7 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { // TODO: merge, they are almost identical val leftUserUpdatesNew = - if (state.typ.isChannel) List( + if (state.groupType.isChannel) List( UpdateGroupCanViewMembersChanged(groupId, canViewMembers = false), UpdateGroupCanEditInfoChanged(groupId, canEditGroup = false), UpdateGroupCanEditUsernameChanged(groupId, canEditUsername = false), @@ -514,7 +514,7 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { ) val membersUpdateNew = - if (state.typ.isChannel) { // if history is shared + if (state.groupType.isChannel) { // if channel, or group is big enough UpdateGroupMembersCountChanged( groupId, membersCount = state.membersCount - 1 @@ -610,7 +610,7 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { // Groups V2 API updates // /////////////////////////// - seqStateDate ← if (state.typ.isChannel) leaveCHANNELUpdates else leaveGROUPUpdates + seqStateDate ← if (state.groupType.isChannel) leaveCHANNELUpdates else leaveGROUPUpdates } yield seqStateDate @@ -634,7 +634,7 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { // TODO: merge, they are almost identical val kickedUserUpdatesNew: List[Update] = - if (state.typ.isChannel) List( + if (state.groupType.isChannel) List( UpdateGroupCanViewMembersChanged(groupId, canViewMembers = false), UpdateGroupCanEditInfoChanged(groupId, canEditGroup = false), UpdateGroupCanEditUsernameChanged(groupId, canEditUsername = false), @@ -657,7 +657,7 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { ) val membersUpdateNew: Update = - if (newState.typ.isChannel) { // if history is shared + if (newState.groupType.isChannel) { // if channel, or group is big enough UpdateGroupMembersCountChanged( groupId, membersCount = newState.membersCount @@ -754,7 +754,7 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { // Groups V2 API updates // /////////////////////////// - seqStateDate ← if (state.typ.isChannel) kickCHANNELUpdates else kickGROUPUpdates + seqStateDate ← if (state.groupType.isChannel) kickCHANNELUpdates else kickGROUPUpdates } yield seqStateDate @@ -1156,7 +1156,7 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { userId = cmd.candidateUserId, update = updateCanEdit ) - seqStateDate ← if (state.typ.isChannel) adminCHANNELUpdates else adminGROUPUpdates + seqStateDate ← if (state.groupType.isChannel) adminCHANNELUpdates else adminGROUPUpdates } yield (members, seqStateDate) @@ -1242,7 +1242,7 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { userId = cmd.targetUserId, update = updateCanEdit ) - seqState ← if (state.typ.isChannel) adminCHANNELUpdates else adminGROUPUpdates + seqState ← if (state.groupType.isChannel) adminCHANNELUpdates else adminGROUPUpdates } yield seqState diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala index 0f13f3694e..78cf3dfe76 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala @@ -62,7 +62,7 @@ trait GroupQueryHandlers { ) } - state.typ match { + state.groupType match { case General | Public ⇒ load case Channel ⇒ if (state.isAdmin(clientUserId)) load @@ -71,7 +71,7 @@ trait GroupQueryHandlers { } protected def isPublic = - FastFuture.successful(IsPublicResponse(isPublic = state.typ == GroupType.Public)) + FastFuture.successful(IsPublicResponse(isPublic = state.groupType == GroupType.Public)) protected def isHistoryShared = FastFuture.successful(IsHistorySharedResponse(state.isHistoryShared)) @@ -99,7 +99,7 @@ trait GroupQueryHandlers { isHidden = Some(state.isHidden), ext = None, membersCount = Some(count), - groupType = Some(state.typ match { + groupType = Some(state.groupType match { case GroupType.Channel ⇒ ApiGroupType.CHANNEL case GroupType.General | GroupType.Public | GroupType.Unrecognized(_) ⇒ ApiGroupType.GROUP }), @@ -141,14 +141,14 @@ trait GroupQueryHandlers { protected def canSendMessage(clientUserId: Int): Future[CanSendMessageResponse] = FastFuture.successful { val canSend = state.bot.exists(_.userId == clientUserId) || { - state.typ match { + state.groupType match { case General | Public ⇒ state.isMember(clientUserId) case Channel ⇒ state.isAdmin(clientUserId) } } CanSendMessageResponse( canSend = canSend, - isChannel = state.typ.isChannel, + isChannel = state.groupType.isChannel, memberIds = state.memberIds.toSeq, botId = state.bot.map(_.userId) ) @@ -187,7 +187,7 @@ trait GroupQueryHandlers { } if (state.isMember(clientUserId)) { - state.typ match { + state.groupType match { case General | Public ⇒ apiMembers → group.membersCount case Channel ⇒ diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala index a14cdf8c86..094895cc55 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala @@ -73,7 +73,7 @@ private[group] object GroupState { avatar = None, topic = None, shortName = None, - typ = GroupType.General, + groupType = GroupType.General, isHidden = false, isHistoryShared = false, members = Map.empty, @@ -101,7 +101,7 @@ private[group] final case class GroupState( avatar: Option[Avatar], topic: Option[String], shortName: Option[String], - typ: GroupType, // TODO: rename to groupType + groupType: GroupType, // TODO: rename to groupType isHidden: Boolean, isHistoryShared: Boolean, @@ -142,13 +142,13 @@ private[group] final case class GroupState( val isCreated = createdAt.nonEmpty def isAsyncMembers = - typ match { + groupType match { case General | Public ⇒ members.size > 100 case Channel ⇒ true } def getShowableOwner(clientUserId: Int): Option[Int] = - typ match { + groupType match { case General | Public ⇒ Some(creatorUserId) case Channel ⇒ if (isAdmin(clientUserId)) Some(creatorUserId) else None } @@ -164,7 +164,7 @@ private[group] final case class GroupState( about = None, avatar = None, topic = None, - typ = evt.typ.getOrElse(GroupType.General), + groupType = evt.typ.getOrElse(GroupType.General), isHidden = evt.isHidden getOrElse false, isHistoryShared = evt.isHistoryShared getOrElse false, members = ( @@ -232,7 +232,7 @@ private[group] final case class GroupState( this.copy(title = newTitle) case BecamePublic(_) ⇒ this.copy( - typ = GroupType.Public, + groupType = GroupType.Public, isHistoryShared = true ) case AboutUpdated(_, newAbout) ⇒ @@ -273,7 +273,7 @@ private[group] final case class GroupState( */ def canSendMessage(clientUserId: Int) = { - typ match { + groupType match { case General | Public ⇒ isMember(clientUserId) case Channel ⇒ isAdmin(clientUserId) || isOwner(clientUserId) } @@ -284,7 +284,7 @@ private[group] final case class GroupState( * in channels, owner and admins can view members */ def canViewMembers(clientUserId: Int) = - typ match { + groupType match { case General | Public ⇒ isMember(clientUserId) case Channel ⇒ isAdmin(clientUserId) || isOwner(clientUserId) } From 2a66255abad4f8c51ef157f9f5b23a022a0bdc06 Mon Sep 17 00:00:00 2001 From: rockjam Date: Tue, 19 Jul 2016 03:14:48 +0300 Subject: [PATCH 143/414] fix(server:groups): delete messages in channels --- .../im/actor/server/dialog/HistoryUtils.scala | 17 ----- .../actor/server/group/GroupOperations.scala | 1 + .../server/group/GroupQueryHandlers.scala | 2 +- .../service/messaging/HistoryHandlers.scala | 67 ++++++++++--------- 4 files changed, 39 insertions(+), 48 deletions(-) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/dialog/HistoryUtils.scala b/actor-server/actor-core/src/main/scala/im/actor/server/dialog/HistoryUtils.scala index 047631dfee..c4f800d492 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/dialog/HistoryUtils.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/dialog/HistoryUtils.scala @@ -98,23 +98,6 @@ object HistoryUtils { } yield () } - // todo: remove this in favor of getHistoryOwner - def withHistoryOwner[A](peer: Peer, clientUserId: Int)(f: Int ⇒ DBIO[A])(implicit system: ActorSystem): DBIO[A] = { - import system.dispatcher - (peer.typ match { - case PeerType.Private ⇒ DBIO.successful(clientUserId) - case PeerType.Group ⇒ - DBIO.from(GroupExtension(system).isHistoryShared(peer.id)) flatMap { isHistoryShared ⇒ - if (isHistoryShared) { - DBIO.successful(SharedUserId) - } else { - DBIO.successful(clientUserId) - } - } - case _ ⇒ throw new RuntimeException(s"Unknown peer type ${peer.typ}") - }) flatMap f - } - def getHistoryOwner(peer: Peer, clientUserId: Int)(implicit system: ActorSystem): Future[Int] = { import system.dispatcher peer.typ match { diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupOperations.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupOperations.scala index 6239b0b4f6..dc56f1a0fd 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupOperations.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupOperations.scala @@ -171,6 +171,7 @@ private[group] sealed trait Queries { GroupEnvelope(groupId) .withGetMembers(GetMembers())).mapTo[GetMembersResponse] map (r ⇒ (r.memberIds, r.invitedUserIds, r.botId)) + // TODO: better name maybe def canSendMessage(groupId: Int, clientUserId: Int): Future[CanSendMessageInfo] = (viewRegion.ref ? GroupEnvelope(groupId) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala index 78cf3dfe76..3b2599df5a 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala @@ -71,7 +71,7 @@ trait GroupQueryHandlers { } protected def isPublic = - FastFuture.successful(IsPublicResponse(isPublic = state.groupType == GroupType.Public)) + FastFuture.successful(IsPublicResponse(state.groupType.isPublic)) protected def isHistoryShared = FastFuture.successful(IsHistorySharedResponse(state.isHistoryShared)) diff --git a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/messaging/HistoryHandlers.scala b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/messaging/HistoryHandlers.scala index 67b4d7a80b..18008066e3 100644 --- a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/messaging/HistoryHandlers.scala +++ b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/messaging/HistoryHandlers.scala @@ -2,7 +2,7 @@ package im.actor.server.api.rpc.service.messaging import java.time.Instant -import com.google.protobuf.wrappers.Int32Value +import akka.http.scaladsl.util.FastFuture import im.actor.api.rpc.PeerHelpers._ import im.actor.api.rpc._ import im.actor.api.rpc.messaging.{ ApiEmptyMessage, _ } @@ -10,7 +10,8 @@ import im.actor.api.rpc.misc.{ ResponseSeq, ResponseVoid } import im.actor.api.rpc.peers.{ ApiGroupOutPeer, ApiOutPeer, ApiPeerType, ApiUserOutPeer } import im.actor.api.rpc.sequence.ApiUpdateOptimization import im.actor.server.dialog.HistoryUtils -import im.actor.server.group.GroupUtils +import im.actor.server.group.GroupQueries.CanSendMessageResponse +import im.actor.server.group.{ CanSendMessageInfo, GroupUtils } import im.actor.server.model.{ DialogObsolete, HistoryMessage, Peer, PeerType } import im.actor.server.persist.contact.UserContactRepo import im.actor.server.persist.dialog.DialogRepo @@ -31,6 +32,8 @@ trait HistoryHandlers { import HistoryUtils._ import Implicits._ + private val CantDelete = Error(CommonRpcErrors.forbidden("You can't delete these messages")) + override def doHandleMessageReceived(peer: ApiOutPeer, date: Long, clientData: im.actor.api.rpc.ClientData): Future[HandlerResult[ResponseVoid]] = { authorized(clientData) { client ⇒ dialogExt.messageReceived(peer.asPeer, client.userId, date) map (_ ⇒ Ok(ResponseVoid)) @@ -238,43 +241,47 @@ trait HistoryHandlers { override def doHandleDeleteMessage(outPeer: ApiOutPeer, randomIds: IndexedSeq[Long], clientData: ClientData): Future[HandlerResult[ResponseSeq]] = authorized(clientData) { implicit client ⇒ - val action = withOutPeerDBIO(outPeer) { + withOutPeer(outPeer) { val peer = outPeer.asModel - withHistoryOwner(peer, client.userId) { historyOwner ⇒ + getHistoryOwner(peer, client.userId) flatMap { historyOwner ⇒ if (isSharedUser(historyOwner)) { - HistoryMessageRepo.find(historyOwner, peer, randomIds.toSet) flatMap { messages ⇒ - if (messages.exists(_.senderUserId != client.userId)) { - DBIO.successful(Error(CommonRpcErrors.forbidden("You can only delete your own messages"))) - } else { - for { - _ ← HistoryMessageRepo.delete(historyOwner, peer, randomIds.toSet) - groupUserIds ← GroupUserRepo.findUserIds(peer.id) map (_.toSet) - SeqState(seq, state) ← DBIO.from(seqUpdExt.broadcastClientUpdate( - client.userId, - client.authId, - bcastUserIds = groupUserIds, - update = UpdateMessageDelete(outPeer.asPeer, randomIds), - pushRules = seqUpdExt.pushRules(isFat = false, None, Seq(client.authId)) - )) - } yield Ok(ResponseSeq(seq, state.toByteArray)) + for { + CanSendMessageInfo(canSend, isChannel, memberIds, _) ← groupExt.canSendMessage(peer.id, client.userId) + result ← (isChannel, canSend) match { + case (true, true) ⇒ // channel, client user is one of those who can send messages, thus he can also delete them. + deleteMessages(peer, historyOwner, randomIds, memberIds) + case (true, false) ⇒ // channel, client user can't send messages, thus he can't delete them. + FastFuture.successful(CantDelete) + case (false, _) ⇒ // not a channel group. Must check if user deletes only messages he sent. + for { + messages ← db.run(HistoryMessageRepo.find(historyOwner, peer, randomIds.toSet)) // TODO: rewrite to ids check only + res ← if (messages.forall(_.senderUserId == client.userId)) { + deleteMessages(peer, historyOwner, randomIds, memberIds) + } else { + FastFuture.successful(CantDelete) + } + } yield res } - } + } yield result } else { - for { - _ ← HistoryMessageRepo.delete(client.userId, peer, randomIds.toSet) - SeqState(seq, state) ← DBIO.from(seqUpdExt.deliverClientUpdate( - client.userId, - client.authId, - update = UpdateMessageDelete(outPeer.asPeer, randomIds), - pushRules = seqUpdExt.pushRules(isFat = false, None) - )) - } yield Ok(ResponseSeq(seq, state.toByteArray)) + deleteMessages(peer, historyOwner, randomIds, otherUsersIds = Set.empty) } } } - db.run(action) } + private def deleteMessages(peer: Peer, historyOwner: Int, randomIds: IndexedSeq[Long], otherUsersIds: Set[Int])(implicit client: AuthorizedClientData) = + for { + _ ← db.run(HistoryMessageRepo.delete(historyOwner, peer, randomIds.toSet)) + SeqState(seq, state) ← seqUpdExt.broadcastClientUpdate( + client.userId, + client.authId, + bcastUserIds = otherUsersIds, + update = UpdateMessageDelete(peer.asStruct, randomIds), + pushRules = seqUpdExt.pushRules(isFat = false, None, excludeAuthIds = Seq(client.authId)) + ) + } yield Ok(ResponseSeq(seq, state.toByteArray)) + private val MaxDateTime = new DateTime(294276, 1, 1, 0, 0) private val MaxDate = MaxDateTime.getMillis From 18ecc60ac6df613bbccb2ac56c787823a051db14 Mon Sep 17 00:00:00 2001 From: rockjam Date: Tue, 19 Jul 2016 04:02:16 +0300 Subject: [PATCH 144/414] fix(server:groups): edit messages in channels; change edit messages rules: can change any own messages in last hour --- .../server/messaging/MessageUpdating.scala | 17 +++++++--- .../server/persist/HistoryMessageRepo.scala | 11 +------ .../service/messaging/MessagingHandlers.scala | 32 ++++++++++++++++--- 3 files changed, 41 insertions(+), 19 deletions(-) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/messaging/MessageUpdating.scala b/actor-server/actor-core/src/main/scala/im/actor/server/messaging/MessageUpdating.scala index 84ec97e2f7..d2744e80e7 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/messaging/MessageUpdating.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/messaging/MessageUpdating.scala @@ -51,10 +51,17 @@ trait MessageUpdating extends PeersImplicits { } yield seqState } - private def updateContentGroup(userId: Int, clientAuthId: Long, peer: Peer, randomId: Long, updatedMessage: ApiMessage, date: Long)(implicit system: ActorSystem): Future[SeqState] = { + private def updateContentGroup( + userId: Int, + clientAuthId: Long, + groupPeer: Peer, + randomId: Long, + updatedMessage: ApiMessage, + date: Long + )(implicit system: ActorSystem): Future[SeqState] = { import system.dispatcher val seqUpdExt = SeqUpdatesExtension(system) - val update = UpdateMessageContentChanged(peer.asStruct, randomId, updatedMessage) + val update = UpdateMessageContentChanged(groupPeer.asStruct, randomId, updatedMessage) for { // update for client user seqState ← seqUpdExt.deliverClientUpdate( @@ -64,8 +71,8 @@ trait MessageUpdating extends PeersImplicits { pushRules = seqUpdExt.pushRules(isFat = false, None), deliveryId = s"msgcontent_${randomId}_${date}" ) - (memberIds, _, _) ← GroupExtension(system).getMemberIds(peer.id) - membersSet = memberIds.toSet + (memberIds, _, optBotId) ← GroupExtension(system).getMemberIds(groupPeer.id) + membersSet = (memberIds ++ optBotId.toSeq).toSet // update for other group members _ ← seqUpdExt.broadcastPeopleUpdate( membersSet - userId, @@ -77,7 +84,7 @@ trait MessageUpdating extends PeersImplicits { userIds = membersSet + userId, randomId = randomId, peerType = PeerType.Group, - peerIds = Set(peer.id), + peerIds = Set(groupPeer.id), messageContentHeader = updatedMessage.header, messageContentData = updatedMessage.toByteArray )) diff --git a/actor-server/actor-persist/src/main/scala/im/actor/server/persist/HistoryMessageRepo.scala b/actor-server/actor-persist/src/main/scala/im/actor/server/persist/HistoryMessageRepo.scala index d8aef50029..4ce5087c68 100644 --- a/actor-server/actor-persist/src/main/scala/im/actor/server/persist/HistoryMessageRepo.scala +++ b/actor-server/actor-persist/src/main/scala/im/actor/server/persist/HistoryMessageRepo.scala @@ -158,15 +158,6 @@ object HistoryMessageRepo { findNewestFilter(userId, peer, filter) } - def findNewestSentBy(userId: Int, peer: Peer): SqlAction[Option[HistoryMessage], NoStream, Read] = { - val filter = { m: HistoryMessageTable ⇒ - m.senderUserId === userId && - m.peerType === peer.typ.value && - m.peerId === peer.id - } - findNewestFilter(userId, peer, filter) - } - private def findNewestFilter(userId: Int, peer: Peer, filterClause: HistoryMessageTable ⇒ Rep[Boolean]) = { notDeletedMessages .filter(filterClause) @@ -246,4 +237,4 @@ object HistoryMessageRepo { .filter(_.randomId inSet randomIds) .map(_.deletedAt) .update(Some(new DateTime)) -} \ No newline at end of file +} diff --git a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/messaging/MessagingHandlers.scala b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/messaging/MessagingHandlers.scala index 898c83f25b..7715069165 100644 --- a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/messaging/MessagingHandlers.scala +++ b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/messaging/MessagingHandlers.scala @@ -9,17 +9,19 @@ import im.actor.api.rpc.messaging._ import im.actor.api.rpc.misc._ import im.actor.api.rpc.peers._ import im.actor.config.ActorConfig +import im.actor.server.group.CanSendMessageInfo import im.actor.server.messaging.{ MessageParsing, MessageUpdating } +import im.actor.server.model.{ Peer, PeerType } import im.actor.server.persist.HistoryMessageRepo import scala.concurrent._ import scala.concurrent.duration._ object MessagingRpcErors { - val NotLastMessage = RpcError(400, "NOT_LAST_MESSAGE", "You are trying to edit not last message.", false, None) val NotInTimeWindow = RpcError(400, "NOT_IN_TIME_WINDOW", "You can't edit message sent more than 5 minutes age.", false, None) val NotTextMessage = RpcError(400, "NOT_TEXT_MESSAGE", "You can edit only text messages.", false, None) val NotUniqueRandomId = RpcError(400, "RANDOM_ID_NOT_UNIQUE", "", false, None) + val NotAllowedToEdit = CommonRpcErrors.forbidden("You are not allowed to edit this message") } private[messaging] trait MessagingHandlers extends PeersImplicits @@ -35,7 +37,7 @@ private[messaging] trait MessagingHandlers extends PeersImplicits private implicit val timeout: Timeout = ActorConfig.defaultTimeout // TODO: configurable - private val editTimeWindow: Long = 5.minutes.toMillis + private val editTimeWindow: Long = 1.hour.toMillis override def doHandleSendMessage( outPeer: ApiOutPeer, @@ -67,8 +69,7 @@ private[messaging] trait MessagingHandlers extends PeersImplicits withOutPeer(outPeer) { val peer = outPeer.asModel (for { - histMessage ← fromFutureOption(CommonRpcErrors.forbidden("Not allowed"))(db.run(HistoryMessageRepo.findNewestSentBy(client.userId, peer))) - _ ← fromBoolean(NotLastMessage)(histMessage.randomId == randomId) + histMessage ← fromFutureOption(NotAllowedToEdit)(getEditableHistoryMessage(peer, randomId)) _ ← fromBoolean(NotInTimeWindow)(inTimeWindow(histMessage.date.getMillis)) apiMessage ← fromXor((e: Any) ⇒ IntenalError)(Xor.fromEither(parseMessage(histMessage.messageContentData))) _ ← fromBoolean(NotTextMessage)(apiMessage match { @@ -82,6 +83,29 @@ private[messaging] trait MessagingHandlers extends PeersImplicits } } + private def getEditableHistoryMessage(peer: Peer, randomId: Long)(implicit client: AuthorizedClientData) = { + def findBySender(senderId: Int) = db.run(HistoryMessageRepo.findBySender(senderId, peer, randomId).headOption) + + for { + optMessage ← peer match { + case Peer(PeerType.Private, _) ⇒ + findBySender(client.userId) + case Peer(PeerType.Group, groupId) ⇒ + for { + CanSendMessageInfo(canSend, isChannel, _, optBotId) ← groupExt.canSendMessage(groupId, client.userId) + mess ← (isChannel, canSend) match { + case (true, true) ⇒ // channel, client user is one of those who can send messages, thus he can also edit message. + (optBotId map findBySender) getOrElse FastFuture.successful(None) + case (true, false) ⇒ // channel, client user can't send messages, thus he can't edit message. + FastFuture.successful(None) + case (false, _) ⇒ // not a channel group. regular, as in case of private peer + findBySender(client.userId) + } + } yield mess + } + } yield optMessage + } + private def inTimeWindow(messageDateMillis: Long): Boolean = { (messageDateMillis + editTimeWindow) > System.currentTimeMillis } From 35f292b86328d88266068614f35e09858e4c9fd6 Mon Sep 17 00:00:00 2001 From: rockjam Date: Tue, 19 Jul 2016 04:24:00 +0300 Subject: [PATCH 145/414] fix(server:rpc,groups): disable typings for channels --- .../actor/server/group/GroupOperations.scala | 1 + .../server/group/GroupQueryHandlers.scala | 2 +- .../rpc/service/weak/WeakServiceImpl.scala | 25 +++++++++++++------ 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupOperations.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupOperations.scala index dc56f1a0fd..f905ede15b 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupOperations.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupOperations.scala @@ -171,6 +171,7 @@ private[group] sealed trait Queries { GroupEnvelope(groupId) .withGetMembers(GetMembers())).mapTo[GetMembersResponse] map (r ⇒ (r.memberIds, r.invitedUserIds, r.botId)) + // TODO: should be signed as internal API // TODO: better name maybe def canSendMessage(groupId: Int, clientUserId: Int): Future[CanSendMessageInfo] = (viewRegion.ref ? diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala index 3b2599df5a..09ce2d4ccb 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala @@ -35,7 +35,7 @@ trait GroupQueryHandlers { protected def getMembers: Future[GetMembersResponse] = FastFuture.successful { GetMembersResponse( - memberIds = state.members.keySet.toSeq, + memberIds = state.memberIds.toSeq, invitedUserIds = state.invitedUserIds.toSeq, botId = state.bot.map(_.userId) ) diff --git a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/weak/WeakServiceImpl.scala b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/weak/WeakServiceImpl.scala index 0ece36d6a8..08fb4fc9ae 100644 --- a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/weak/WeakServiceImpl.scala +++ b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/weak/WeakServiceImpl.scala @@ -8,7 +8,7 @@ import im.actor.api.rpc.peers.{ ApiOutPeer, ApiPeer, ApiPeerType } import im.actor.api.rpc.weak._ import im.actor.concurrent.FutureExt import im.actor.server.db.DbExtension -import im.actor.server.group.GroupExtension +import im.actor.server.group.{ CanSendMessageInfo, GroupExtension } import im.actor.server.presences.PresenceExtension import im.actor.server.sequence.WeakUpdatesExtension import slick.driver.PostgresDriver.api._ @@ -28,7 +28,6 @@ class WeakServiceImpl(implicit actorSystem: ActorSystem) extends WeakService { authorized(clientData) { client ⇒ peer.`type` match { case ApiPeerType.EncryptedPrivate ⇒ - val update = UpdateTyping(ApiPeer(ApiPeerType.EncryptedPrivate, client.userId), client.userId, typingType) val reduceKey = weakUpdatesExt.reduceKey(update.header, update.peer) @@ -43,8 +42,11 @@ class WeakServiceImpl(implicit actorSystem: ActorSystem) extends WeakService { val reduceKey = weakUpdatesExt.reduceKey(update.header, update.peer, client.userId) for { - (memberIds, _, _) ← groupExt.getMemberIds(peer.id) - _ ← FutureExt.ftraverse(memberIds filterNot (_ == client.userId))(weakUpdatesExt.broadcastUserWeakUpdate(_, update, Some(reduceKey))) + CanSendMessageInfo(_, isChannel, memberIds, _) ← groupExt.canSendMessage(peer.id, client.userId) + _ ← if (isChannel) + FastFuture.successful(()) + else + FutureExt.ftraverse((memberIds - client.userId).toSeq)(weakUpdatesExt.broadcastUserWeakUpdate(_, update, Some(reduceKey))) } yield () } @@ -80,17 +82,26 @@ class WeakServiceImpl(implicit actorSystem: ActorSystem) extends WeakService { override def doHandleStopTyping(peer: ApiOutPeer, typingType: ApiTypingType.ApiTypingType, clientData: ClientData): Future[HandlerResult[ResponseVoid]] = { authorized(clientData) { client ⇒ peer.`type` match { + case ApiPeerType.EncryptedPrivate ⇒ + val update = UpdateTypingStop(ApiPeer(ApiPeerType.EncryptedPrivate, client.userId), client.userId, typingType) + val reduceKey = weakUpdatesExt.reduceKey(update.header, update.peer) + + weakUpdatesExt.broadcastUserWeakUpdate(peer.id, update, reduceKey = Some(reduceKey)) case ApiPeerType.Private ⇒ val update = UpdateTypingStop(ApiPeer(ApiPeerType.Private, client.userId), client.userId, typingType) val reduceKey = weakUpdatesExt.reduceKey(update.header, update.peer) - weakUpdatesExt.broadcastUserWeakUpdate(peer.id, update, Some(reduceKey)) + + weakUpdatesExt.broadcastUserWeakUpdate(peer.id, update, reduceKey = Some(reduceKey)) case ApiPeerType.Group ⇒ val update = UpdateTypingStop(ApiPeer(ApiPeerType.Group, peer.id), client.userId, typingType) val reduceKey = weakUpdatesExt.reduceKey(update.header, update.peer) for { - (memberIds, _, _) ← groupExt.getMemberIds(peer.id) - _ ← FutureExt.ftraverse(memberIds filterNot (_ == client.userId))(weakUpdatesExt.broadcastUserWeakUpdate(_, update, Some(reduceKey))) + CanSendMessageInfo(_, isChannel, memberIds, _) ← groupExt.canSendMessage(peer.id, client.userId) + _ ← if (isChannel) + FastFuture.successful(()) + else + FutureExt.ftraverse((memberIds - client.userId).toSeq)(weakUpdatesExt.broadcastUserWeakUpdate(_, update, Some(reduceKey))) } yield () } From 95ed7984592d5ceb3e790cac472b8eef74d17685 Mon Sep 17 00:00:00 2001 From: rockjam Date: Tue, 19 Jul 2016 04:56:34 +0300 Subject: [PATCH 146/414] fix(server:groups): tests, group permissions, let users of plain groups to change group topic --- .../scala/im/actor/server/group/GroupCommandHandlers.scala | 7 +++++-- .../main/scala/im/actor/server/group/GroupProcessor.scala | 3 ++- .../src/main/scala/im/actor/server/group/GroupState.scala | 4 ++-- .../actor/server/api/rpc/service/GroupsServiceSpec.scala | 4 ++-- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala index 6b8e259e7c..d07382bcab 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala @@ -879,13 +879,16 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { } } + //TODO: who can update topic??? protected def updateTopic(cmd: UpdateTopic): Unit = { def isValidTopic(topic: Option[String]) = topic.forall(_.length < 255) val topic = trimToEmpty(cmd.topic) - if (!state.permissions.canEditInfo(cmd.clientUserId)) { - sender() ! noPermission + if (state.groupType.isChannel && !state.isAdmin(cmd.clientUserId)) { + sender() ! notAdmin + } else if (state.nonMember(cmd.clientUserId)) { + sender() ! notMember } else if (!isValidTopic(topic)) { sender() ! Status.Failure(TopicTooLong) } else { diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala index dbf2f948a3..cea7d2dc66 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala @@ -87,7 +87,8 @@ object GroupProcessor { 22016 → classOf[GroupEvents.IntegrationTokenRevoked], 22017 → classOf[GroupEvents.OwnerChanged], 22018 → classOf[GroupEvents.ShortNameUpdated], - 22019 → classOf[GroupEvents.AdminSettingsUpdated] + 22019 → classOf[GroupEvents.AdminSettingsUpdated], + 22020 → classOf[GroupEvents.AdminStatusChanged] ) def persistenceIdFor(groupId: Int): String = s"Group-${groupId}" diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala index 094895cc55..3404476df4 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala @@ -26,8 +26,8 @@ object AdminSettings { val Default = AdminSettings( showAdminsToMembers = true, canMembersInvite = true, - canMembersEditGroupInfo = true, - canAdminsEditGroupInfo = false + canMembersEditGroupInfo = false, + canAdminsEditGroupInfo = true ) // format: OFF diff --git a/actor-server/actor-tests/src/test/scala/im/actor/server/api/rpc/service/GroupsServiceSpec.scala b/actor-server/actor-tests/src/test/scala/im/actor/server/api/rpc/service/GroupsServiceSpec.scala index 5d070d51ea..2a78c860e4 100644 --- a/actor-server/actor-tests/src/test/scala/im/actor/server/api/rpc/service/GroupsServiceSpec.scala +++ b/actor-server/actor-tests/src/test/scala/im/actor/server/api/rpc/service/GroupsServiceSpec.scala @@ -703,7 +703,7 @@ final class GroupsServiceSpec } whenReady(service.handleMakeUserAdmin(groupOutPeer, user3OutPeer)(clientData2)) { resp ⇒ - resp shouldEqual Error(CommonRpcErrors.forbidden("Only admin can perform this action.")) + resp shouldEqual Error(GroupRpcErrors.NoPermission) } } @@ -761,7 +761,7 @@ final class GroupsServiceSpec } whenReady(service.handleEditGroupAbout(groupOutPeer, 1L, Some("It is group for fun"), Vector.empty)(clientData2)) { resp ⇒ - resp shouldEqual Error(CommonRpcErrors.forbidden("Only admin can perform this action.")) + resp shouldEqual Error(GroupRpcErrors.NoPermission) } } From 804d0c59ab15bb334d4117fa8c212ce17c067ddd Mon Sep 17 00:00:00 2001 From: rockjam Date: Tue, 19 Jul 2016 05:59:01 +0300 Subject: [PATCH 147/414] fix(server:groups): don't subscribe to channels onlines, change permissions check for GetIntegrationToken --- .../src/main/protobuf/groupV2.proto | 9 ++++ .../actor/server/group/GroupOperations.scala | 5 +++ .../actor/server/group/GroupProcessor.scala | 3 ++ .../server/group/GroupQueryHandlers.scala | 16 +++++-- .../presences/GroupPresenceManager.scala | 8 ++-- .../rpc/service/groups/GroupRpcErrors.scala | 5 +-- .../sequence/SequenceServiceImpl.scala | 45 ++++++++++++------- .../webhooks/IntegrationsServiceImpl.scala | 7 +-- 8 files changed, 70 insertions(+), 28 deletions(-) diff --git a/actor-server/actor-core/src/main/protobuf/groupV2.proto b/actor-server/actor-core/src/main/protobuf/groupV2.proto index 8ca35e184d..e5c725e071 100644 --- a/actor-server/actor-core/src/main/protobuf/groupV2.proto +++ b/actor-server/actor-core/src/main/protobuf/groupV2.proto @@ -45,6 +45,7 @@ message GroupEnvelope { GroupQueries.GetMembers get_members = 18; // internalGetMembers GroupQueries.LoadMembers load_members = 19; GroupQueries.IsPublic is_public = 20; + GroupQueries.IsChannel is_channel = 31; GroupQueries.IsHistoryShared is_history_shared = 21; GroupQueries.GetApiStruct get_api_struct = 22; GroupQueries.GetApiFullStruct get_api_full_struct = 23; @@ -258,6 +259,14 @@ message GroupQueries { bool is_public = 1; } + message IsChannel { + option (scalapb.message).extends = "GroupQuery"; + } + + message IsChannelResponse { + bool is_channel = 1; + } + message IsHistoryShared { option (scalapb.message).extends = "GroupQuery"; } diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupOperations.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupOperations.scala index f905ede15b..627adf3aff 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupOperations.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupOperations.scala @@ -153,6 +153,11 @@ private[group] sealed trait Queries { GroupEnvelope(groupId) .withIsPublic(IsPublic())).mapTo[IsPublicResponse] map (_.isPublic) + def isChannel(groupId: Int): Future[Boolean] = + (viewRegion.ref ? + GroupEnvelope(groupId) + .withIsChannel(IsChannel())).mapTo[IsChannelResponse] map (_.isChannel) + def isHistoryShared(groupId: Int): Future[Boolean] = (viewRegion.ref ? GroupEnvelope(groupId) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala index cea7d2dc66..46dc0dea6a 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala @@ -71,6 +71,8 @@ object GroupProcessor { 21020 → classOf[GroupQueries.CanSendMessageResponse], 21021 → classOf[GroupQueries.LoadAdminSettings], 21022 → classOf[GroupQueries.LoadAdminSettingsResponse], + 21023 → classOf[GroupQueries.IsChannel], + 21024 → classOf[GroupQueries.IsChannelResponse], 22003 → classOf[GroupEvents.UserInvited], 22004 → classOf[GroupEvents.UserJoined], @@ -168,6 +170,7 @@ private[group] final class GroupProcessor case GetMembers() ⇒ getMembers case LoadMembers(clientUserId, limit, offset) ⇒ loadMembers(clientUserId, limit, offset) case IsPublic() ⇒ isPublic + case IsChannel() ⇒ isChannel case IsHistoryShared() ⇒ isHistoryShared case GetApiStruct(clientUserId) ⇒ getApiStruct(clientUserId) case GetApiFullStruct(clientUserId) ⇒ getApiFullStruct(clientUserId) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala index 09ce2d4ccb..f92db7fa2e 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala @@ -6,7 +6,7 @@ import akka.stream.scaladsl.Source import com.google.protobuf.ByteString import com.google.protobuf.wrappers.Int32Value import im.actor.api.rpc.groups._ -import im.actor.server.group.GroupErrors.NotOwner +import im.actor.server.group.GroupErrors.{ NoPermission, NotOwner } import im.actor.server.group.GroupQueries._ import im.actor.server.group.GroupType.{ Channel, General, Public } @@ -25,9 +25,14 @@ trait GroupQueryHandlers { protected def getIntegrationToken(optUserId: Option[Int]): Future[GetIntegrationTokenResponse] = { val canViewToken = optUserId.forall(state.isAdmin) - FastFuture.successful(GetIntegrationTokenResponse( - if (canViewToken) state.bot.map(_.token) else None - )) + val allowedToView = optUserId.forall(state.isMember) + if (allowedToView) { + FastFuture.successful(GetIntegrationTokenResponse( + if (canViewToken) state.bot.map(_.token) else None + )) + } else { + FastFuture.failed(NoPermission) + } } //TODO: do something with this method. Will this method used in "client" context. @@ -73,6 +78,9 @@ trait GroupQueryHandlers { protected def isPublic = FastFuture.successful(IsPublicResponse(state.groupType.isPublic)) + protected def isChannel = + FastFuture.successful(IsChannelResponse(state.groupType.isChannel)) + protected def isHistoryShared = FastFuture.successful(IsHistorySharedResponse(state.isHistoryShared)) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/presences/GroupPresenceManager.scala b/actor-server/actor-core/src/main/scala/im/actor/server/presences/GroupPresenceManager.scala index 2d2142d4bc..a785898b13 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/presences/GroupPresenceManager.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/presences/GroupPresenceManager.scala @@ -5,6 +5,7 @@ import akka.cluster.sharding.ShardRegion.Passivate import akka.pattern.pipe import akka.util.Timeout import im.actor.server.db.DbExtension +import im.actor.server.group.GroupExtension import im.actor.server.persist.GroupUserRepo import slick.driver.PostgresDriver.api._ @@ -54,6 +55,7 @@ class GroupPresenceManager extends Actor with ActorLogging with Stash { implicit val timeout = Timeout(5.seconds) private val db: Database = DbExtension(context.system).db + private lazy val groupExt = GroupExtension(context.system) private val presenceExt = PresenceExtension(context.system) private val receiveTimeout = 15.minutes // TODO: configurable @@ -67,8 +69,8 @@ class GroupPresenceManager extends Actor with ActorLogging with Stash { def receive = { case env @ Envelope(groupId, _) ⇒ stash() - db.run(GroupUserRepo.findUserIds(groupId)) - .map(ids ⇒ Initialized(groupId, ids.toSet)) + groupExt.getMemberIds(groupId) + .map { case (ids, _, _) ⇒ Initialized(groupId, ids.toSet) } .pipeTo(self) .onFailure { case e ⇒ @@ -146,4 +148,4 @@ class GroupPresenceManager extends Actor with ActorLogging with Stash { private def deliverState(groupId: Int, consumer: ActorRef): Unit = consumer ! GroupPresenceState(groupId, onlineUserIds.size) -} \ No newline at end of file +} diff --git a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupRpcErrors.scala b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupRpcErrors.scala index 8505b3ba81..83e425bab7 100644 --- a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupRpcErrors.scala +++ b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupRpcErrors.scala @@ -1,6 +1,6 @@ package im.actor.server.api.rpc.service.groups -import im.actor.api.rpc.RpcError +import im.actor.api.rpc.{ CommonRpcErrors, RpcError } // format: OFF object GroupRpcErrors { @@ -18,10 +18,9 @@ object GroupRpcErrors { val InvalidInviteToken = RpcError(403, "INVALID_INVITE_TOKEN", "Invalid invite token!", false, None) val InvalidInviteGroup = RpcError(403, "INVALID_INVITE_GROUP", "Invalid group name provided!", false, None) val GroupNotPublic = RpcError(400, "GROUP_IS_NOT_PUBLIC", "The group is not public.", false, None) - val NoPermission = RpcError(403, "NO_PERMISSION", "You have no permission to execute this action", false, None) val InvalidShortName = RpcError(400, "GROUP_SHORT_NAME_INVALID", "Invalid group short name. Valid short name should contain from 5 to 32 characters, and may consist of latin characters, numbers and underscores", false, None) val ShortNameTaken = RpcError(400, "GROUP_SHORT_NAME_TAKEN", "This short name already belongs to other user or group, we are sorry!", false, None) - + val NoPermission = CommonRpcErrors.forbidden("You have no permission to execute this action") } // format: ON diff --git a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/sequence/SequenceServiceImpl.scala b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/sequence/SequenceServiceImpl.scala index 96728a2905..4c15592915 100644 --- a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/sequence/SequenceServiceImpl.scala +++ b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/sequence/SequenceServiceImpl.scala @@ -14,6 +14,7 @@ import akka.http.scaladsl.util.FastFuture import im.actor.api.rpc._ import im.actor.api.rpc.misc.{ ResponseSeq, ResponseVoid } import im.actor.api.rpc.peers.{ ApiGroupOutPeer, ApiUserOutPeer } +import im.actor.concurrent.FutureExt import im.actor.server.db.DbExtension import im.actor.server.group.{ GroupExtension, GroupUtils } import im.actor.server.sequence.{ Difference, SeqState, SeqUpdatesExtension } @@ -27,12 +28,14 @@ final class SequenceServiceImpl(config: SequenceServiceConfig)( actorSystem: ActorSystem ) extends SequenceService { import FutureResultRpc._ + import PeerHelpers._ protected override implicit val ec: ExecutionContext = actorSystem.dispatcher private val log = Logging(actorSystem, getClass) private val db: Database = DbExtension(actorSystem).db - private implicit val seqUpdExt: SeqUpdatesExtension = SeqUpdatesExtension(actorSystem) + private val seqUpdExt = SeqUpdatesExtension(actorSystem) + private val groupExt = GroupExtension(actorSystem) private val maxDifferenceSize: Long = config.maxDifferenceSize @@ -135,22 +138,34 @@ final class SequenceServiceImpl(config: SequenceServiceConfig)( } } - override def doHandleSubscribeToGroupOnline(groups: IndexedSeq[ApiGroupOutPeer], clientData: ClientData): Future[HandlerResult[ResponseVoid]] = { - FastFuture.successful(Ok(ResponseVoid)) andThen { - case _ ⇒ - // FIXME: #security check access hashes - sessionRegion.ref ! SessionEnvelope(clientData.authId, clientData.sessionId) - .withSubscribeToGroupOnline(SubscribeToGroupOnline(groups.map(_.groupId))) + override def doHandleSubscribeToGroupOnline(groups: IndexedSeq[ApiGroupOutPeer], clientData: ClientData): Future[HandlerResult[ResponseVoid]] = + withGroupOutPeers(groups) { + FastFuture.successful(Ok(ResponseVoid)) andThen { + case _ ⇒ + getNonChannelsIds(groups) foreach { groupIds ⇒ + sessionRegion.ref ! SessionEnvelope(clientData.authId, clientData.sessionId) + .withSubscribeToGroupOnline(SubscribeToGroupOnline(groupIds)) + } + } } - } - override def doHandleSubscribeFromGroupOnline(groups: IndexedSeq[ApiGroupOutPeer], clientData: ClientData): Future[HandlerResult[ResponseVoid]] = { - FastFuture.successful(Ok(ResponseVoid)) andThen { - case _ ⇒ - // FIXME: #security check access hashes - sessionRegion.ref ! SessionEnvelope(clientData.authId, clientData.sessionId) - .withSubscribeFromGroupOnline(SubscribeFromGroupOnline(groups.map(_.groupId))) + override def doHandleSubscribeFromGroupOnline(groups: IndexedSeq[ApiGroupOutPeer], clientData: ClientData): Future[HandlerResult[ResponseVoid]] = + withGroupOutPeers(groups) { + FastFuture.successful(Ok(ResponseVoid)) andThen { + case _ ⇒ + getNonChannelsIds(groups) foreach { groupIds ⇒ + sessionRegion.ref ! SessionEnvelope(clientData.authId, clientData.sessionId) + .withSubscribeFromGroupOnline(SubscribeFromGroupOnline(groupIds)) + } + + } } + + private def getNonChannelsIds(groups: Seq[ApiGroupOutPeer]): Future[Seq[Int]] = { + FutureExt.ftraverse(groups) { + case ApiGroupOutPeer(groupId, _) ⇒ + groupExt.isChannel(groupId) map (isChannel ⇒ if (isChannel) None else Some(groupId)) + } map (_.flatten) } //TODO: remove @@ -185,7 +200,7 @@ final class SequenceServiceImpl(config: SequenceServiceConfig)( FastFuture.successful((Vector.empty, Vector.empty)) else for { - groups ← Future.sequence(groupIds.toVector map (GroupExtension(actorSystem).getApiStruct(_, client.userId))) + groups ← Future.sequence(groupIds.toVector map (groupExt.getApiStruct(_, client.userId))) // TODO: #perf optimize collection operations allUserIds = userIds ++ groups.foldLeft(Vector.empty[Int]) { (ids, g) ⇒ ids ++ g.members.flatMap(m ⇒ Vector(m.userId, m.inviterUserId)) :+ g.creatorUserId } users ← Future.sequence(allUserIds.toVector map (UserUtils.safeGetUser(_, client.userId, client.authId))) map (_.flatten) diff --git a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/webhooks/IntegrationsServiceImpl.scala b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/webhooks/IntegrationsServiceImpl.scala index cfa1ca5f71..ddaec3d85a 100644 --- a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/webhooks/IntegrationsServiceImpl.scala +++ b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/webhooks/IntegrationsServiceImpl.scala @@ -9,7 +9,7 @@ import im.actor.api.rpc.integrations.{ IntegrationsService, ResponseIntegrationT import im.actor.api.rpc.peers.{ ApiOutPeer, ApiPeerType } import im.actor.server.api.rpc.service.webhooks.IntegrationServiceHelpers._ import im.actor.server.db.DbExtension -import im.actor.server.group.GroupErrors.{ NotAMember, NotAdmin } +import im.actor.server.group.GroupErrors.{ NoPermission, NotAMember, NotAdmin } import im.actor.server.group.GroupExtension import slick.driver.PostgresDriver.api._ @@ -55,8 +55,9 @@ class IntegrationsServiceImpl(baseUri: String)(implicit actorSystem: ActorSystem } override def onFailure: PartialFunction[Throwable, RpcError] = { - case NotAdmin ⇒ CommonRpcErrors.forbidden("Only admin can perform this action.") - case NotAMember ⇒ CommonRpcErrors.forbidden("You are not a group member.") + case NotAdmin ⇒ CommonRpcErrors.forbidden("Only admin can perform this action.") + case NotAMember ⇒ CommonRpcErrors.forbidden("You are not a group member.") + case NoPermission ⇒ CommonRpcErrors.forbidden("You have no permission to execute this action") } } From d95e18f69ff12e1470b722dfe00b369e5d54fb36 Mon Sep 17 00:00:00 2001 From: rockjam Date: Tue, 19 Jul 2016 06:02:37 +0300 Subject: [PATCH 148/414] refactor(rpc): remove deprecated PubGroup service --- .../actor/server/persist/GroupUserRepo.scala | 8 -- .../pubgroups/PubgroupsServiceImpl.scala | 46 ------- .../scala/im/actor/server/ActorServer.scala | 5 - .../rpc/service/PubgroupsServiceSpec.scala | 129 ------------------ 4 files changed, 188 deletions(-) delete mode 100644 actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/pubgroups/PubgroupsServiceImpl.scala delete mode 100644 actor-server/actor-tests/src/test/scala/im/actor/server/api/rpc/service/PubgroupsServiceSpec.scala diff --git a/actor-server/actor-persist/src/main/scala/im/actor/server/persist/GroupUserRepo.scala b/actor-server/actor-persist/src/main/scala/im/actor/server/persist/GroupUserRepo.scala index f195941789..ad018f0433 100644 --- a/actor-server/actor-persist/src/main/scala/im/actor/server/persist/GroupUserRepo.scala +++ b/actor-server/actor-persist/src/main/scala/im/actor/server/persist/GroupUserRepo.scala @@ -56,14 +56,6 @@ object GroupUserRepo { def find(groupId: Int, userId: Int) = byPKC((groupId, userId)).result.headOption - //TODO: remove - def exists(groupId: Int, userId: Int) = - byPKC.applied((groupId, userId)).exists.result - - //TODO: revisit later - def findUserIds(groupId: Int) = - userIdByGroupIdC(groupId).result - @deprecated("Duplication of event-sourced groups logic", "2016-06-05") def delete(groupId: Int, userId: Int): FixedSqlAction[Int, NoStream, Write] = byPKC.applied((groupId, userId)).delete diff --git a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/pubgroups/PubgroupsServiceImpl.scala b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/pubgroups/PubgroupsServiceImpl.scala deleted file mode 100644 index 4a81f7e148..0000000000 --- a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/pubgroups/PubgroupsServiceImpl.scala +++ /dev/null @@ -1,46 +0,0 @@ -package im.actor.server.api.rpc.service.pubgroups - -import im.actor.server.group.GroupUtils -import im.actor.server.persist.{ AvatarDataRepo, GroupRepo, GroupUserRepo } - -import scala.concurrent.{ ExecutionContext, Future } -import akka.actor.ActorSystem -import slick.driver.PostgresDriver.api._ -import im.actor.api.rpc._ -import im.actor.api.rpc.pubgroups.{ ApiPublicGroup, PubgroupsService, ResponseGetPublicGroups } -import im.actor.server.ApiConversions -import im.actor.server.db.DbExtension -import im.actor.server.file.ImageUtils -import im.actor.server.model.Group -import im.actor.server.persist.contact.UserContactRepo - -class PubgroupsServiceImpl(implicit actorSystem: ActorSystem) extends PubgroupsService { - import ApiConversions._ - import ImageUtils._ - - override implicit val ec: ExecutionContext = actorSystem.dispatcher - - override def doHandleGetPublicGroups(clientData: ClientData): Future[HandlerResult[ResponseGetPublicGroups]] = { - authorized(clientData) { implicit client ⇒ - val action = for { - groups ← GroupRepo.findPublic - pubGroupStructs ← DBIO.sequence(groups map (g ⇒ getPubgroupStruct(g, client.userId))) - sorted = pubGroupStructs.sortWith((g1, g2) ⇒ g1.friendsCount >= g2.friendsCount && g1.membersCount >= g2.membersCount) - } yield Ok(ResponseGetPublicGroups(sorted.toVector)) - DbExtension(actorSystem).db.run(action) - } - } - - def getPubgroupStruct(group: Group, userId: Int)(implicit ec: ExecutionContext): DBIO[ApiPublicGroup] = { - for { - membersIds ← GroupUserRepo.findUserIds(group.id) - userContactsIds ← UserContactRepo.findNotDeletedIds(userId) - friendsCount = (membersIds intersect userContactsIds).length - groupAvatarModelOpt ← AvatarDataRepo.findByGroupId(group.id) - } yield { - ApiPublicGroup(group.id, group.accessHash, group.title, membersIds.length, friendsCount, group.about.getOrElse(""), groupAvatarModelOpt map getAvatar) - } - } - -} - diff --git a/actor-server/actor-server-sdk/src/main/scala/im/actor/server/ActorServer.scala b/actor-server/actor-server-sdk/src/main/scala/im/actor/server/ActorServer.scala index 230caa9d38..10a1a5c10b 100644 --- a/actor-server/actor-server-sdk/src/main/scala/im/actor/server/ActorServer.scala +++ b/actor-server/actor-server-sdk/src/main/scala/im/actor/server/ActorServer.scala @@ -22,7 +22,6 @@ import im.actor.server.api.rpc.service.groups.{ GroupInviteConfig, GroupsService import im.actor.server.api.rpc.service.messaging.MessagingServiceImpl import im.actor.server.api.rpc.service.privacy.PrivacyServiceImpl import im.actor.server.api.rpc.service.profile.ProfileServiceImpl -import im.actor.server.api.rpc.service.pubgroups.PubgroupsServiceImpl import im.actor.server.api.rpc.service.push.PushServiceImpl import im.actor.server.api.rpc.service.sequence.{ SequenceServiceConfig, SequenceServiceImpl } import im.actor.server.api.rpc.service.stickers.StickersServiceImpl @@ -195,9 +194,6 @@ final case class ActorServerBuilder(defaultConfig: Config = ConfigFactory.empty( system.log.debug("Starting GroupsService") val groupsService = new GroupsServiceImpl(groupInviteConfig) - system.log.debug("Starting PubgroupsService") - val pubgroupsService = new PubgroupsServiceImpl - system.log.debug("Starting SequenceService") val sequenceService = new SequenceServiceImpl(sequenceConfig) @@ -251,7 +247,6 @@ final case class ActorServerBuilder(defaultConfig: Config = ConfigFactory.empty( contactsService, messagingService, groupsService, - pubgroupsService, sequenceService, weakService, usersService, diff --git a/actor-server/actor-tests/src/test/scala/im/actor/server/api/rpc/service/PubgroupsServiceSpec.scala b/actor-server/actor-tests/src/test/scala/im/actor/server/api/rpc/service/PubgroupsServiceSpec.scala deleted file mode 100644 index ae1f6ac6a5..0000000000 --- a/actor-server/actor-tests/src/test/scala/im/actor/server/api/rpc/service/PubgroupsServiceSpec.scala +++ /dev/null @@ -1,129 +0,0 @@ -package im.actor.server.api.rpc.service - -import im.actor.api.rpc._ -import im.actor.api.rpc.pubgroups.ResponseGetPublicGroups -import im.actor.server._ -import im.actor.server.acl.ACLUtils -import im.actor.server.acl.ACLUtils.userAccessHash -import im.actor.server.api.rpc.service.contacts.ContactsServiceImpl -import im.actor.server.api.rpc.service.groups.{ GroupInviteConfig, GroupsServiceImpl } -import im.actor.server.api.rpc.service.pubgroups.PubgroupsServiceImpl -import im.actor.server.api.rpc.service.sequence.{ SequenceServiceConfig, SequenceServiceImpl } -import org.scalatest.Inside._ - -final class PubgroupsServiceSpec - extends BaseAppSuite - with GroupsServiceHelpers - with MessageParsing - with ImplicitSessionRegion - with ImplicitAuthService { - behavior of "PubgroupsService" - - it should "include number of friends in PubGroup" in pendingUntilFixed(t.e1) - - it should "list all public groups with descrition" in pendingUntilFixed(t.e2) - - it should "sort pubgroups by friends count and members count" in pendingUntilFixed(t.e3) - - it should "show number of members and friends to any non-member" in pendingUntilFixed(t.e4) - - val groupInviteConfig = GroupInviteConfig("http://actor.im") - val sequenceConfig = SequenceServiceConfig.load().toOption.get - - val sequenceService = new SequenceServiceImpl(sequenceConfig) - val messagingService = messaging.MessagingServiceImpl() - implicit val groupService = new GroupsServiceImpl(groupInviteConfig) - val pubGroupService = new PubgroupsServiceImpl - val contactService = new ContactsServiceImpl() - - object t { - val (user1, authId1, authSid1, _) = createUser() - val (user2, _, _, _) = createUser() - val (user3, _, _, _) = createUser() - val (user4, _, _, _) = createUser() - val (user5, _, _, _) = createUser() - val (user6, _, _, _) = createUser() - val (user7, _, _, _) = createUser() - val (user8, authId8, authSid8, _) = createUser() - - val sessionId = createSessionId() - implicit val clientData = ClientData(authId1, sessionId, Some(AuthData(user1.id, authSid1, 42))) - - val descriptions = List("Marvelous group for android developers group", "Group for iOS users", "You know it") - - val androidGroup = createPubGroup("Android group", descriptions(0), Set(user2.id, user4.id)).groupPeer - val iosGroup = createPubGroup("iOS group", descriptions(1), Set(user2.id, user3.id, user4.id, user5.id, user6.id, user7.id)).groupPeer - val scalaGroup = createPubGroup("Scala group", descriptions(2), Set(user2.id, user5.id, user6.id, user7.id, user8.id)).groupPeer - val floodGroup = createPubGroup("Flood group", descriptions(2), Set(user2.id, user3.id, user5.id, user6.id, user7.id)).groupPeer - - def e1() = { - whenReady(contactService.handleAddContact(user2.id, userAccessHash(clientData.authId, user2.id, getUserModel(user2.id).accessSalt)))(_ ⇒ ()) - whenReady(pubGroupService.handleGetPublicGroups()) { resp ⇒ - inside(resp) { - case Ok(ResponseGetPublicGroups(groups)) ⇒ - val group = groups.find(_.id == androidGroup.groupId) - group shouldBe defined - group.get.friendsCount shouldEqual 1 - } - } - - whenReady(contactService.handleAddContact(user3.id, userAccessHash(clientData.authId, user3.id, getUserModel(user3.id).accessSalt)))(_ ⇒ ()) //not in group. should not be in friends - whenReady(contactService.handleAddContact(user4.id, userAccessHash(clientData.authId, user4.id, getUserModel(user4.id).accessSalt)))(_ ⇒ ()) - - whenReady(pubGroupService.handleGetPublicGroups()) { resp ⇒ - inside(resp) { - case Ok(ResponseGetPublicGroups(groups)) ⇒ - val group = groups.find(_.id == androidGroup.groupId) - group shouldBe defined - group.get.friendsCount shouldEqual 2 - } - } - } - - def e2() = { - whenReady(pubGroupService.handleGetPublicGroups()) { resp ⇒ - inside(resp) { - case Ok(ResponseGetPublicGroups(groups)) ⇒ - groups should have length 4 - groups.map(_.description).toSet shouldEqual descriptions.toSet - } - } - } - - def e3() = { - /** - * Sorting according number of friends and members - * ios - friends = 3; members = 7 - * flood - friends = 2; members = 6 - * android - friends = 2; members = 3 - * scala - friends = 1; members = 6 - */ - whenReady(pubGroupService.handleGetPublicGroups()) { resp ⇒ - inside(resp) { - case Ok(ResponseGetPublicGroups(groups)) ⇒ - groups.map(_.id) shouldEqual List(iosGroup, floodGroup, androidGroup, scalaGroup).map(_.groupId) - } - } - } - - def e4() = { - implicit val clientData = ClientData(authId8, sessionId, Some(AuthData(user8.id, authSid8, 42))) - whenReady(contactService.handleAddContact(user2.id, userAccessHash(clientData.authId, user2.id, getUserModel(user2.id).accessSalt)))(_ ⇒ ()) - whenReady(contactService.handleAddContact(user3.id, userAccessHash(clientData.authId, user3.id, getUserModel(user3.id).accessSalt)))(_ ⇒ ()) - whenReady(contactService.handleAddContact(user4.id, userAccessHash(clientData.authId, user4.id, getUserModel(user4.id).accessSalt)))(_ ⇒ ()) - - whenReady(pubGroupService.handleGetPublicGroups()) { resp ⇒ - inside(resp) { - case Ok(ResponseGetPublicGroups(groups)) ⇒ - groups.find(_.id == floodGroup.groupId) foreach { g ⇒ - g.friendsCount shouldEqual 2 - g.membersCount shouldEqual 6 - } - //sorting should be the same as in previous example cause we got same contacts - groups.map(_.id) shouldEqual List(iosGroup, floodGroup, androidGroup, scalaGroup).map(_.groupId) - } - } - } - } - -} From 95103fee452fb0de9581e87a5bad87ca0f3302d4 Mon Sep 17 00:00:00 2001 From: rockjam Date: Tue, 19 Jul 2016 06:18:34 +0300 Subject: [PATCH 149/414] chore(server): update actor.json --- .../actor-core/src/main/actor-api/actor.json | 82 ++++++++++--------- 1 file changed, 45 insertions(+), 37 deletions(-) diff --git a/actor-server/actor-core/src/main/actor-api/actor.json b/actor-server/actor-core/src/main/actor-api/actor.json index 7219e2b553..6246ebdfc2 100644 --- a/actor-server/actor-core/src/main/actor-api/actor.json +++ b/actor-server/actor-core/src/main/actor-api/actor.json @@ -19620,7 +19620,7 @@ { "type": "reference", "argument": "keyGroupId", - "category": "hidden", + "category": "full", "description": " Key Group Id" } ], @@ -19644,6 +19644,45 @@ ] } }, + { + "type": "struct", + "content": { + "name": "KeyGroupHolder", + "doc": [ + "Key Group Holder", + { + "type": "reference", + "argument": "uid", + "category": "full", + "description": " User's id" + }, + { + "type": "reference", + "argument": "keyGroup", + "category": "full", + "description": " Key Group" + } + ], + "attributes": [ + { + "type": { + "type": "alias", + "childType": "userId" + }, + "id": 1, + "name": "uid" + }, + { + "type": { + "type": "struct", + "childType": "EncryptionKeyGroup" + }, + "id": 2, + "name": "keyGroup" + } + ] + } + }, { "type": "rpc", "content": { @@ -19653,18 +19692,6 @@ "type": "anonymous", "header": 2664, "doc": [ - { - "type": "reference", - "argument": "seq", - "category": "full", - "description": " seq" - }, - { - "type": "reference", - "argument": "state", - "category": "full", - "description": " state" - }, { "type": "reference", "argument": "date", @@ -19675,7 +19702,7 @@ "type": "reference", "argument": "obsoleteKeyGroups", "category": "full", - "description": " obsolete key groups" + "description": " obsolete key group ids" }, { "type": "reference", @@ -19685,25 +19712,6 @@ } ], "attributes": [ - { - "type": { - "type": "opt", - "childType": "int32" - }, - "id": 1, - "name": "seq" - }, - { - "type": { - "type": "opt", - "childType": { - "type": "alias", - "childType": "seq_state" - } - }, - "id": 2, - "name": "state" - }, { "type": { "type": "opt", @@ -19712,7 +19720,7 @@ "childType": "date" } }, - "id": 3, + "id": 1, "name": "date" }, { @@ -19723,7 +19731,7 @@ "childType": "KeyGroupId" } }, - "id": 4, + "id": 2, "name": "obsoleteKeyGroups" }, { @@ -19731,10 +19739,10 @@ "type": "list", "childType": { "type": "struct", - "childType": "KeyGroupId" + "childType": "KeyGroupHolder" } }, - "id": 5, + "id": 3, "name": "missedKeyGroups" } ] From b3af81ff7079fb180b4ce3bec7d8081f3b0e30a6 Mon Sep 17 00:00:00 2001 From: rockjam Date: Tue, 19 Jul 2016 06:24:18 +0300 Subject: [PATCH 150/414] fix(server:rpc): respond with missing key group instead of key group id --- .../im/actor/server/encryption/EncryptionExtension.scala | 9 ++++----- .../rpc/service/encryption/EncryptionServiceImpl.scala | 4 ---- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/encryption/EncryptionExtension.scala b/actor-server/actor-core/src/main/scala/im/actor/server/encryption/EncryptionExtension.scala index 8393bd8db9..3a68237efe 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/encryption/EncryptionExtension.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/encryption/EncryptionExtension.scala @@ -6,7 +6,6 @@ import akka.http.scaladsl.util.FastFuture import cats.std.all._ import cats.syntax.all._ import cats.data.{ Xor, XorT } -import com.google.protobuf.wrappers.Int32Value import im.actor.api.rpc.encryption._ import im.actor.cats.dbio._ import im.actor.server.db.DbExtension @@ -212,8 +211,8 @@ final class EncryptionExtension(system: ActorSystem) extends Extension { def checkBox( box: ApiEncryptedBox, ignoredKeyGroups: Map[Int, Set[Int]] - ): Future[Either[(Vector[ApiKeyGroupId], Vector[ApiKeyGroupId]), Map[Int, Vector[(Long, ApiEncryptedBox)]]]] = { - val userChecksFu: Iterable[Future[(Seq[ApiKeyGroupId], Seq[ApiKeyGroupId], Seq[EncryptionKeyGroup])]] = + ): Future[Either[(Vector[ApiKeyGroupHolder], Vector[ApiKeyGroupId]), Map[Int, Vector[(Long, ApiEncryptedBox)]]]] = { + val userChecksFu: Iterable[Future[(Seq[ApiKeyGroupHolder], Seq[ApiKeyGroupId], Seq[EncryptionKeyGroup])]] = box.keys.groupBy(_.usersId) map { case (userId, keys) ⇒ db.run(EncryptionKeyGroupRepo.fetch(userId)) map { kgs ⇒ @@ -223,7 +222,7 @@ final class EncryptionExtension(system: ActorSystem) extends Extension { val missingKgs = kgs.view .filterNot(kg ⇒ keys.exists(_.keyGroupId == kg.id)) .filterNot(kg ⇒ ignored.contains(kg.id)) - .map(kg ⇒ ApiKeyGroupId(userId, kg.id)) + .flatMap(kg ⇒ toApi(kg).toOption map (ApiKeyGroupHolder(userId, _))) .force // kgs presented in box but deleted by receiver @@ -237,7 +236,7 @@ final class EncryptionExtension(system: ActorSystem) extends Extension { Future.sequence(userChecksFu) map { checks ⇒ val (missing, obs, kgs) = - checks.foldLeft((Vector.empty[ApiKeyGroupId], Vector.empty[ApiKeyGroupId], Vector.empty[EncryptionKeyGroup])) { + checks.foldLeft((Vector.empty[ApiKeyGroupHolder], Vector.empty[ApiKeyGroupId], Vector.empty[EncryptionKeyGroup])) { case ((macc, oacc, kgacc), (m, o, kg)) ⇒ (macc ++ m, oacc ++ o, kgacc ++ kg) } diff --git a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/encryption/EncryptionServiceImpl.scala b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/encryption/EncryptionServiceImpl.scala index 37be9d987e..24fe5a5732 100644 --- a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/encryption/EncryptionServiceImpl.scala +++ b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/encryption/EncryptionServiceImpl.scala @@ -121,8 +121,6 @@ final class EncryptionServiceImpl(implicit system: ActorSystem) extends Encrypti encExt.checkBox(encryptedBox, ignoredKeyGroups.groupBy(_.userId).mapValues(_.map(_.keyGroupId).toSet)) flatMap { case Left((missing, obs)) ⇒ FastFuture.successful(Ok(ResponseSendEncryptedPackage( - seq = None, - state = None, date = None, obsoleteKeyGroups = obs, missedKeyGroups = missing @@ -159,8 +157,6 @@ final class EncryptionServiceImpl(implicit system: ActorSystem) extends Encrypti case None ⇒ updExt.deliverClientUpdate(client.userId, client.authId, UpdateEmptyUpdate) } } yield Ok(ResponseSendEncryptedPackage( - seq = Some(seqState.seq), - state = Some(seqState.state.toByteArray), date = Some(date), Vector.empty, Vector.empty From e17f31d3f7e2a8fc01ec8a155419c73b011a88a7 Mon Sep 17 00:00:00 2001 From: rockjam Date: Tue, 19 Jul 2016 07:28:51 +0300 Subject: [PATCH 151/414] fix(server:presence): return back deprecated method --- .../im/actor/server/presences/GroupPresenceManager.scala | 6 ++---- .../main/scala/im/actor/server/persist/GroupUserRepo.scala | 4 ++++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/presences/GroupPresenceManager.scala b/actor-server/actor-core/src/main/scala/im/actor/server/presences/GroupPresenceManager.scala index a785898b13..c4041aed4d 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/presences/GroupPresenceManager.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/presences/GroupPresenceManager.scala @@ -5,7 +5,6 @@ import akka.cluster.sharding.ShardRegion.Passivate import akka.pattern.pipe import akka.util.Timeout import im.actor.server.db.DbExtension -import im.actor.server.group.GroupExtension import im.actor.server.persist.GroupUserRepo import slick.driver.PostgresDriver.api._ @@ -55,7 +54,6 @@ class GroupPresenceManager extends Actor with ActorLogging with Stash { implicit val timeout = Timeout(5.seconds) private val db: Database = DbExtension(context.system).db - private lazy val groupExt = GroupExtension(context.system) private val presenceExt = PresenceExtension(context.system) private val receiveTimeout = 15.minutes // TODO: configurable @@ -69,8 +67,8 @@ class GroupPresenceManager extends Actor with ActorLogging with Stash { def receive = { case env @ Envelope(groupId, _) ⇒ stash() - groupExt.getMemberIds(groupId) - .map { case (ids, _, _) ⇒ Initialized(groupId, ids.toSet) } + db.run(GroupUserRepo.findUserIds(groupId)) + .map(ids ⇒ Initialized(groupId, ids.toSet)) .pipeTo(self) .onFailure { case e ⇒ diff --git a/actor-server/actor-persist/src/main/scala/im/actor/server/persist/GroupUserRepo.scala b/actor-server/actor-persist/src/main/scala/im/actor/server/persist/GroupUserRepo.scala index ad018f0433..1a828f9827 100644 --- a/actor-server/actor-persist/src/main/scala/im/actor/server/persist/GroupUserRepo.scala +++ b/actor-server/actor-persist/src/main/scala/im/actor/server/persist/GroupUserRepo.scala @@ -52,6 +52,10 @@ object GroupUserRepo { def find(groupId: Int) = byGroupIdC(groupId).result + //TODO: revisit later + def findUserIds(groupId: Int) = + userIdByGroupIdC(groupId).result + @deprecated("Compatibility with old group API, remove when possible", "2016-06-05") def find(groupId: Int, userId: Int) = byPKC((groupId, userId)).result.headOption From a79379f3ed809d1df6d6f4b519ff6a510bd8ab34 Mon Sep 17 00:00:00 2001 From: rockjam Date: Tue, 19 Jul 2016 14:18:27 +0300 Subject: [PATCH 152/414] fix(server:persist): ignore case for nickname storage --- .../server/names/GlobalNamesStorage.scala | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/names/GlobalNamesStorage.scala b/actor-server/actor-core/src/main/scala/im/actor/server/names/GlobalNamesStorage.scala index 261cf44223..a24cc6b529 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/names/GlobalNamesStorage.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/names/GlobalNamesStorage.scala @@ -10,9 +10,9 @@ import slick.dbio._ import scala.concurrent.Future /** - * Stores mapping "Global name" -> "Global name owner(group/user)" - * global name: String - * global name owner: GlobalNameOwner + * Stores mapping "Normalized global name" -> "Global name owner(group/user)" + * normalized global name: String + * global name owner: im.actor.server.names.GlobalNameOwner */ private object GlobalNamesStorage extends SimpleStorage("global_names") @@ -43,7 +43,7 @@ final class GlobalNamesStorageKeyValueStorage(implicit system: ActorSystem) { * Compatible with storing nicknames in `im.actor.server.persist.UserRepo` */ def exists(name: String): Future[Boolean] = { - val existsInKV = conn.run(GlobalNamesStorage.get(name)) map (_.isDefined) + val existsInKV = conn.run(GlobalNamesStorage.get(normalized(name))) map (_.isDefined) existsInKV flatMap { case true ⇒ FastFuture.successful(true) @@ -70,10 +70,9 @@ final class GlobalNamesStorageKeyValueStorage(implicit system: ActorSystem) { */ private def getOwner(name: String): Future[Option[GlobalNameOwner]] = { val optOwner = conn.run( - GlobalNamesStorage.get(name) - ) map { optBytes ⇒ - optBytes map GlobalNameOwner.parseFrom - } + GlobalNamesStorage.get(normalized(name)) + ) map { _ map GlobalNameOwner.parseFrom } + optOwner flatMap { case o @ Some(_) ⇒ FastFuture.successful(o) case None ⇒ db.run(UserRepo.findByNickname(name)) map (_.map(u ⇒ GlobalNameOwner(OwnerType.User, u.id))) @@ -82,17 +81,17 @@ final class GlobalNamesStorageKeyValueStorage(implicit system: ActorSystem) { private def upsert(name: String, owner: GlobalNameOwner): Future[Unit] = conn.run( - GlobalNamesStorage.upsert(name, owner.toByteArray) + GlobalNamesStorage.upsert(normalized(name), owner.toByteArray) ) map (_ ⇒ ()) /** * Compatible with storing nicknames in `im.actor.server.persist.UserRepo` */ private def delete(name: String): Future[Unit] = { - val kvDelete = conn.run(GlobalNamesStorage.delete(name)) + val kvDelete = conn.run(GlobalNamesStorage.delete(normalized(name))) kvDelete flatMap { count ⇒ - if (count > 0) { + if (count == 0) { db.run { for { optUser ← UserRepo.findByNickname(name) @@ -107,4 +106,7 @@ final class GlobalNamesStorageKeyValueStorage(implicit system: ActorSystem) { } } } + + private def normalized(name: String) = name.toLowerCase + } From 68212a6a051feaabcf4835805be997eed78bd3af Mon Sep 17 00:00:00 2001 From: rockjam Date: Tue, 19 Jul 2016 17:52:01 +0300 Subject: [PATCH 153/414] feat,fix(groups): make group with shared history when short name set; allow group members to change group info by default --- .../actor-core/src/main/protobuf/group.proto | 8 +- .../src/main/protobuf/groupV2.proto | 18 +- .../server/group/AdminCommandHandlers.scala | 322 +++++ .../server/group/GroupCommandHandlers.scala | 1241 +---------------- .../actor/server/group/GroupOperations.scala | 10 +- .../actor/server/group/GroupProcessor.scala | 6 +- .../server/group/GroupQueryHandlers.scala | 17 +- .../im/actor/server/group/GroupState.scala | 48 +- .../server/group/InfoCommandHandlers.scala | 337 +++++ .../server/group/MemberCommandHandlers.scala | 645 +++++++++ .../service/groups/GroupsServiceImpl.scala | 6 +- .../service/search/SearchServiceImpl.scala | 11 +- .../src/main/scala/im/actor/server/Main.scala | 2 +- 13 files changed, 1391 insertions(+), 1280 deletions(-) create mode 100644 actor-server/actor-core/src/main/scala/im/actor/server/group/AdminCommandHandlers.scala create mode 100644 actor-server/actor-core/src/main/scala/im/actor/server/group/InfoCommandHandlers.scala create mode 100644 actor-server/actor-core/src/main/scala/im/actor/server/group/MemberCommandHandlers.scala diff --git a/actor-server/actor-core/src/main/protobuf/group.proto b/actor-server/actor-core/src/main/protobuf/group.proto index cdfc8d94b4..bfc8566ae2 100644 --- a/actor-server/actor-core/src/main/protobuf/group.proto +++ b/actor-server/actor-core/src/main/protobuf/group.proto @@ -12,7 +12,6 @@ import "file.proto"; enum GroupType { General = 1; - Public = 2; Channel = 3; } @@ -149,4 +148,11 @@ message GroupEvents { required int64 ts = 1 [(scalapb.field).type = "java.time.Instant"]; required int32 settings_bit_mask = 2; } + + message HistoryBecameShared { + option (scalapb.message).extends = "im.actor.server.group.GroupEvent"; + + required int64 ts = 1 [(scalapb.field).type = "java.time.Instant"]; + required int32 executor_user_id = 2; + } } diff --git a/actor-server/actor-core/src/main/protobuf/groupV2.proto b/actor-server/actor-core/src/main/protobuf/groupV2.proto index e5c725e071..2f8eaee56c 100644 --- a/actor-server/actor-core/src/main/protobuf/groupV2.proto +++ b/actor-server/actor-core/src/main/protobuf/groupV2.proto @@ -31,7 +31,7 @@ message GroupEnvelope { GroupCommands.UpdateTopic update_topic = 9; GroupCommands.UpdateAbout update_about = 10; GroupCommands.UpdateShortName update_short_name = 27; -// GroupCommands.MakePublic make_public = 11; + GroupCommands.MakeHistoryShared make_history_shared = 32; GroupCommands.RevokeIntegrationToken revoke_token = 12; GroupCommands.MakeUserAdmin make_user_admin = 13; GroupCommands.DismissUserAdmin dismiss_user_admin = 28; @@ -44,7 +44,6 @@ message GroupEnvelope { GroupQueries.GetIntegrationToken get_integration_token = 17; GroupQueries.GetMembers get_members = 18; // internalGetMembers GroupQueries.LoadMembers load_members = 19; - GroupQueries.IsPublic is_public = 20; GroupQueries.IsChannel is_channel = 31; GroupQueries.IsHistoryShared is_history_shared = 21; GroupQueries.GetApiStruct get_api_struct = 22; @@ -198,6 +197,13 @@ message GroupCommands { } message UpdateAdminSettingsAck {} + + message MakeHistoryShared { + option (scalapb.message).extends = "GroupCommand"; + + int32 client_user_id = 1; + int64 client_auth_id = 2; + } } message GroupQueries { @@ -251,14 +257,6 @@ message GroupQueries { google.protobuf.Int32Value bot_id = 3; } - message IsPublic { - option (scalapb.message).extends = "GroupQuery"; - } - - message IsPublicResponse { - bool is_public = 1; - } - message IsChannel { option (scalapb.message).extends = "GroupQuery"; } diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/AdminCommandHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/AdminCommandHandlers.scala new file mode 100644 index 0000000000..33d1833f89 --- /dev/null +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/AdminCommandHandlers.scala @@ -0,0 +1,322 @@ +package im.actor.server.group + +import java.time.Instant + +import akka.actor.Status +import akka.pattern.pipe +import akka.http.scaladsl.util.FastFuture +import im.actor.api.rpc.{ PeersImplicits, Update } +import im.actor.api.rpc.groups._ +import im.actor.concurrent.FutureExt +import im.actor.server.CommonErrors +import im.actor.server.acl.ACLUtils +import im.actor.server.group.GroupCommands.{ DismissUserAdmin, MakeHistoryShared, MakeUserAdmin, RevokeIntegrationToken, RevokeIntegrationTokenAck, TransferOwnership, UpdateAdminSettings, UpdateAdminSettingsAck } +import im.actor.server.group.GroupErrors.{ NotAMember, NotAdmin, UserAlreadyAdmin, UserAlreadyNotAdmin } +import im.actor.server.group.GroupEvents.{ AdminSettingsUpdated, AdminStatusChanged, HistoryBecameShared, IntegrationTokenRevoked, OwnerChanged } +import im.actor.server.persist.{ GroupBotRepo, GroupUserRepo } +import im.actor.server.sequence.{ SeqState, SeqStateDate } + +import scala.concurrent.Future + +private[group] trait AdminCommandHandlers extends GroupsImplicits { + this: GroupProcessor ⇒ + + protected def revokeIntegrationToken(cmd: RevokeIntegrationToken): Unit = { + if (!(state.isAdmin(cmd.clientUserId) || state.isOwner(cmd.clientUserId))) { + sender() ! notAdmin + } else { + val oldToken = state.bot.map(_.token) + val newToken = ACLUtils.accessToken() + + persist(IntegrationTokenRevoked(Instant.now, newToken)) { evt ⇒ + val newState = commit(evt) + + //TODO: remove deprecated + db.run(GroupBotRepo.updateToken(groupId, newToken)) + + val result: Future[RevokeIntegrationTokenAck] = for { + _ ← oldToken match { + case Some(token) ⇒ integrationStorage.deleteToken(token) + case None ⇒ FastFuture.successful(()) + } + _ ← integrationStorage.upsertToken(newToken, groupId) + } yield RevokeIntegrationTokenAck(newToken) + + result pipeTo sender() + } + } + } + + protected def makeUserAdmin(cmd: MakeUserAdmin): Unit = { + if (!state.permissions.canEditAdmins(cmd.clientUserId)) { + sender() ! noPermission + } else if (state.nonMember(cmd.candidateUserId)) { + sender() ! Status.Failure(NotAMember) + } else if (state.isAdmin(cmd.candidateUserId)) { + sender() ! Status.Failure(UserAlreadyAdmin) + } else { + persist(AdminStatusChanged(Instant.now, cmd.candidateUserId, isAdmin = true)) { evt ⇒ + val newState = commit(evt) + + val dateMillis = evt.ts.toEpochMilli + val memberIds = newState.memberIds + val members = newState.members.values.map(_.asStruct).toVector + + val updateAdmin = UpdateGroupMemberAdminChanged(groupId, cmd.candidateUserId, isAdmin = true) + val updateMembers = UpdateGroupMembersUpdated(groupId, members) + // now this user is admin, change edit rules for admins + val updateCanEdit = UpdateGroupCanEditInfoChanged(groupId, canEditGroup = newState.adminSettings.canAdminsEditGroupInfo) + + val updateObsolete = UpdateGroupMembersUpdateObsolete(groupId, members) + + //TODO: remove deprecated + db.run(GroupUserRepo.makeAdmin(groupId, cmd.candidateUserId)) + + val adminGROUPUpdates: Future[SeqStateDate] = + for { + _ ← seqUpdExt.broadcastPeopleUpdate( + userIds = memberIds + cmd.clientUserId, + updateAdmin + ) + SeqState(seq, state) ← seqUpdExt.broadcastClientUpdate( + cmd.clientUserId, + cmd.clientAuthId, + memberIds - cmd.clientUserId, + updateMembers + ) + } yield SeqStateDate(seq, state, dateMillis) + + val adminCHANNELUpdates: Future[SeqStateDate] = + for { + // push admin changed to all + _ ← seqUpdExt.broadcastPeopleUpdate( + userIds = memberIds + cmd.clientUserId, + updateAdmin + ) + // push changed members to admins and fresh admin + SeqState(seq, state) ← seqUpdExt.broadcastClientUpdate( + cmd.clientUserId, + cmd.clientAuthId, + (newState.adminIds - cmd.clientUserId) + cmd.candidateUserId, + updateMembers + ) + } yield SeqStateDate(seq, state, dateMillis) + + val result: Future[(Vector[ApiMember], SeqStateDate)] = for { + + /////////////////////////// + // Groups V1 API updates // + /////////////////////////// + + _ ← seqUpdExt.broadcastClientUpdate( + cmd.clientUserId, + cmd.clientAuthId, + memberIds - cmd.clientUserId, + updateObsolete + ) + + /////////////////////////// + // Groups V2 API updates // + /////////////////////////// + _ ← seqUpdExt.deliverUserUpdate( + userId = cmd.candidateUserId, + update = updateCanEdit + ) + seqStateDate ← if (state.groupType.isChannel) adminCHANNELUpdates else adminGROUPUpdates + + } yield (members, seqStateDate) + + result pipeTo sender() + } + } + } + + protected def dismissUserAdmin(cmd: DismissUserAdmin): Unit = { + if (!state.permissions.canEditAdmins(cmd.clientUserId)) { + sender() ! noPermission + } else if (state.nonMember(cmd.targetUserId)) { + sender() ! Status.Failure(NotAMember) + } else if (!state.isAdmin(cmd.targetUserId)) { + sender() ! Status.Failure(UserAlreadyNotAdmin) + } else { + persist(AdminStatusChanged(Instant.now, cmd.targetUserId, isAdmin = false)) { evt ⇒ + val newState = commit(evt) + + val dateMillis = evt.ts.toEpochMilli + val memberIds = newState.memberIds + val members = newState.members.values.map(_.asStruct).toVector + + val updateAdmin = UpdateGroupMemberAdminChanged(groupId, cmd.targetUserId, isAdmin = false) + val updateMembers = UpdateGroupMembersUpdated(groupId, members) + // now this user is not admin, change edit rules to plain members + val updateCanEdit = UpdateGroupCanEditInfoChanged(groupId, canEditGroup = newState.adminSettings.canMembersEditGroupInfo) + + val updateObsolete = UpdateGroupMembersUpdateObsolete(groupId, members) + + //TODO: remove deprecated + db.run(GroupUserRepo.dismissAdmin(groupId, cmd.targetUserId)) + + val adminGROUPUpdates: Future[SeqState] = + for { + // push admin changed to all + _ ← seqUpdExt.broadcastPeopleUpdate( + userIds = memberIds + cmd.clientUserId, + updateAdmin + ) + // push changed members to all users + seqState ← seqUpdExt.broadcastClientUpdate( + cmd.clientUserId, + cmd.clientAuthId, + memberIds - cmd.clientUserId, + updateMembers + ) + } yield seqState + + val adminCHANNELUpdates: Future[SeqState] = + for { + // push admin changed to all + _ ← seqUpdExt.broadcastPeopleUpdate( + userIds = memberIds + cmd.clientUserId, + updateAdmin + ) + // push changed members to admins and fresh admin + seqState ← seqUpdExt.broadcastClientUpdate( + cmd.clientUserId, + cmd.clientAuthId, + newState.adminIds - cmd.clientUserId, + updateMembers + ) + } yield seqState + + val result: Future[SeqState] = for { + + /////////////////////////// + // Groups V1 API updates // + /////////////////////////// + + _ ← seqUpdExt.broadcastClientUpdate( + cmd.clientUserId, + cmd.clientAuthId, + memberIds - cmd.clientUserId, + updateObsolete + ) + + /////////////////////////// + // Groups V2 API updates // + /////////////////////////// + _ ← seqUpdExt.deliverUserUpdate( + userId = cmd.targetUserId, + update = updateCanEdit + ) + seqState ← if (state.groupType.isChannel) adminCHANNELUpdates else adminGROUPUpdates + + } yield seqState + + result pipeTo sender() + } + } + } + + protected def transferOwnership(cmd: TransferOwnership): Unit = { + if (!state.isOwner(cmd.clientUserId)) { + sender() ! Status.Failure(CommonErrors.Forbidden) + } else { + persist(OwnerChanged(Instant.now, cmd.newOwnerId)) { evt ⇒ + val newState = commit(evt) + val memberIds = newState.memberIds + + val result: Future[SeqState] = for { + seqState ← seqUpdExt.broadcastClientUpdate( + userId = cmd.clientUserId, + authId = cmd.clientAuthId, + bcastUserIds = memberIds - cmd.clientUserId, + update = UpdateGroupOwnerChanged(groupId, cmd.newOwnerId), + pushRules = seqUpdExt.pushRules(isFat = false, None) + ) + } yield seqState + + result pipeTo sender() + } + } + } + + protected def updateAdminSettings(cmd: UpdateAdminSettings): Unit = { + if (!state.permissions.canEditAdminSettings(cmd.clientUserId)) { + sender() ! Status.Failure(NotAdmin) + } else { + val settOld = state.adminSettings + + persist(AdminSettingsUpdated(Instant.now, cmd.settingsBitMask)) { evt ⇒ + val newState = commit(evt) + val settNew = newState.adminSettings + + val (membersUpdates, adminsUpdates) = { + // push to all members except admins and owner + val showAdminToMembers = PartialFunction.condOpt(settOld.showAdminsToMembers != settNew.showAdminsToMembers) { + case true ⇒ UpdateGroupCanViewAdminsChanged(groupId, canViewAdmins = settNew.showAdminsToMembers) + } + + // push to all members except admins and owner + val canMembersInvite = PartialFunction.condOpt(settOld.canMembersInvite != settNew.canMembersInvite) { + case true ⇒ UpdateGroupCanInviteMembersChanged(groupId, canInviteMembers = settNew.canMembersInvite) + } + + // push to all members except admins and owner + val canMembersEditGroupInfo = PartialFunction.condOpt(settOld.canMembersEditGroupInfo != settNew.canMembersEditGroupInfo) { + case true ⇒ UpdateGroupCanEditInfoChanged(groupId, canEditGroup = settNew.canMembersEditGroupInfo) + } + + // push to admins only + val canAdminsEditGroupInfo = PartialFunction.condOpt(settOld.canAdminsEditGroupInfo != settNew.canAdminsEditGroupInfo) { + case true ⇒ UpdateGroupCanEditInfoChanged(groupId, canEditGroup = settNew.canAdminsEditGroupInfo) + } + + ( + List(showAdminToMembers, canMembersInvite, canMembersEditGroupInfo).flatten[Update], + List(canAdminsEditGroupInfo).flatten[Update] + ) + } + + val plainMemberIds = (newState.memberIds - newState.ownerUserId) -- newState.adminIds + val adminsOnlyIds = newState.adminIds - newState.ownerUserId + + val result: Future[UpdateAdminSettingsAck] = for { + // deliver updates about settings changed to plain group members + _ ← FutureExt.ftraverse(membersUpdates) { update ⇒ + seqUpdExt.broadcastPeopleUpdate(userIds = plainMemberIds, update) + } + // deliver updates about settings changed to plain group members + _ ← FutureExt.ftraverse(membersUpdates) { update ⇒ + seqUpdExt.broadcastPeopleUpdate(userIds = adminsOnlyIds, update) + } + } yield UpdateAdminSettingsAck() + + result pipeTo sender() + } + } + } + + protected def makeHistoryShared(cmd: MakeHistoryShared): Unit = { + if (!state.permissions.canMakeHistoryShared(cmd.clientUserId)) { + sender() ! noPermission + } else { + persist(HistoryBecameShared(Instant.now, cmd.clientUserId)) { evt ⇒ + val newState = commit(evt) + + val memberIds = newState.memberIds + + val result: Future[SeqState] = for { + seqState ← seqUpdExt.broadcastClientUpdate( + userId = cmd.clientUserId, + authId = cmd.clientAuthId, + bcastUserIds = memberIds - cmd.clientUserId, + update = UpdateGroupHistoryShared(groupId) + ) + } yield seqState + + result pipeTo sender() + } + } + } + +} diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala index d07382bcab..86fd4d7541 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala @@ -1,41 +1,36 @@ package im.actor.server.group -import java.time.{ Instant, LocalDateTime, ZoneOffset } +import java.time.Instant import akka.actor.Status import akka.http.scaladsl.util.FastFuture import akka.pattern.pipe -import im.actor.api.rpc.Update -import im.actor.api.rpc.files.ApiAvatar import im.actor.api.rpc.groups._ -import im.actor.api.rpc.messaging.{ ApiServiceMessage, UpdateMessage } import im.actor.api.rpc.users.ApiSex import im.actor.concurrent.FutureExt -import im.actor.server.CommonErrors import im.actor.server.acl.ACLUtils import im.actor.server.dialog.UserAcl -import im.actor.server.file.{ Avatar, ImageUtils } +import im.actor.server.group.GroupCommands._ import im.actor.server.group.GroupErrors._ import im.actor.server.group.GroupEvents._ -import im.actor.server.group.GroupCommands._ -import im.actor.server.model.{ AvatarData, Group } -import im.actor.server.names.{ GlobalNameOwner, OwnerType } -import im.actor.server.persist.{ AvatarDataRepo, GroupBotRepo, GroupInviteTokenRepo, GroupRepo, GroupUserRepo } -import im.actor.server.sequence.{ Optimization, SeqState, SeqStateDate } +import im.actor.server.model.Group +import im.actor.server.persist.{ GroupBotRepo, GroupRepo, GroupUserRepo } +import im.actor.server.sequence.Optimization import im.actor.util.ThreadLocalSecureRandom -import im.actor.util.misc.{ IdUtils, StringUtils } +import im.actor.util.misc.IdUtils import scala.concurrent.Future -//TODO: spit into MemberCommandHandlers - InfoCommandHandlers - AdminCommandHandlers -private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { +private[group] trait GroupCommandHandlers + extends MemberCommandHandlers + with InfoCommandHandlers + with AdminCommandHandlers + with UserAcl { this: GroupProcessor ⇒ - import im.actor.server.ApiConversions._ - - private val notMember = Status.Failure(NotAMember) - private val notAdmin = Status.Failure(NotAdmin) - private val noPermission = Status.Failure(NoPermission) + protected val notMember = Status.Failure(NotAMember) + protected val notAdmin = Status.Failure(NotAdmin) + protected val noPermission = Status.Failure(NoPermission) protected def create(cmd: Create): Unit = { if (!isValidTitle(cmd.title)) { @@ -64,8 +59,7 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { integrationStorage = new IntegrationTokensKeyValueStorage // Group creation - val groupType = GroupType.fromValue(cmd.typ) //FIXME: make it normal enum - val isHistoryShared = groupType.isChannel || groupType.isPublic + val groupType = GroupType.fromValue(cmd.typ) persist(Created( ts = createdAt, @@ -76,7 +70,7 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { title = cmd.title, userIds = Seq(cmd.creatorUserId), // only creator user becomes group member. all other users are invited via Invite message isHidden = Some(false), - isHistoryShared = Some(isHistoryShared), + isHistoryShared = Some(groupType.isChannel), extensions = Seq.empty )) { evt ⇒ val newState = commit(evt) @@ -101,7 +95,7 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { creatorUserId = newState.creatorUserId, accessHash = newState.accessHash, title = newState.title, - isPublic = isHistoryShared, + isPublic = false, createdAt = evt.ts, about = None, topic = None @@ -169,1205 +163,6 @@ private[group] trait GroupCommandHandlers extends GroupsImplicits with UserAcl { } } - protected def invite(cmd: Invite): Unit = { - if (!state.permissions.canInvitePeople(cmd.inviterUserId)) { - sender() ! noPermission - } else if (state.isInvited(cmd.inviteeUserId)) { - sender() ! Status.Failure(GroupErrors.UserAlreadyInvited) - } else if (state.isMember(cmd.inviteeUserId)) { - sender() ! Status.Failure(GroupErrors.UserAlreadyJoined) - } else { - val inviteeIsExUser = state.isExUser(cmd.inviteeUserId) - - persist(UserInvited(Instant.now, cmd.inviteeUserId, cmd.inviterUserId)) { evt ⇒ - val newState = commit(evt) - - val dateMillis = evt.ts.toEpochMilli - val memberIds = newState.memberIds - val apiMembers = newState.members.values.map(_.asStruct).toVector - - // if user ever been in this group - we should push these updates, - val inviteeUpdatesNew: List[Update] = refreshGroupUpdates(newState, cmd.inviteeUserId) - - val membersUpdateNew: Update = - if (newState.groupType.isChannel) // if channel, or group is big enough - UpdateGroupMembersCountChanged(groupId, newState.membersCount) - else - UpdateGroupMembersUpdated(groupId, apiMembers) - - val inviteeUpdateObsolete = UpdateGroupInviteObsolete( - groupId, - inviteUserId = cmd.inviterUserId, - date = dateMillis, - randomId = cmd.randomId - ) - - val membersUpdateObsolete = UpdateGroupUserInvitedObsolete( - groupId, - userId = cmd.inviteeUserId, - inviterUserId = cmd.inviterUserId, - date = dateMillis, - randomId = cmd.randomId - ) - val serviceMessage = GroupServiceMessages.userInvited(cmd.inviteeUserId) - - //TODO: remove deprecated - db.run(GroupUserRepo.create(groupId, cmd.inviteeUserId, cmd.inviterUserId, evt.ts, None, isAdmin = false)) - - def inviteGROUPUpdates: Future[SeqStateDate] = - for { - // push updated members list to inviteeUserId, - // make it `FatSeqUpdate` if this user invited to group for first time. - _ ← seqUpdExt.deliverUserUpdate( - userId = cmd.inviteeUserId, - membersUpdateNew, - pushRules = seqUpdExt.pushRules(isFat = !inviteeIsExUser, Some(PushTexts.Invited)), - deliveryId = s"invite_${groupId}_${cmd.randomId}" - ) - - // push all "refresh group" updates to inviteeUserId - _ ← FutureExt.ftraverse(inviteeUpdatesNew) { update ⇒ - seqUpdExt.deliverUserUpdate(userId = cmd.inviteeUserId, update) - } - - // push updated members list to all group members except inviteeUserId - SeqState(seq, state) ← seqUpdExt.broadcastClientUpdate( - userId = cmd.inviterUserId, - authId = cmd.inviterAuthId, - bcastUserIds = (memberIds - cmd.inviterUserId) - cmd.inviteeUserId, - update = membersUpdateNew, - deliveryId = s"useradded_${groupId}_${cmd.randomId}" - ) - - // explicitly send service message - SeqStateDate(_, _, date) ← dialogExt.sendServerMessage( - apiGroupPeer, - cmd.inviterUserId, - cmd.inviterAuthId, - cmd.randomId, - serviceMessage, - deliveryTag = Some(Optimization.GroupV2) - ) - } yield SeqStateDate(seq, state, date) - - def inviteCHANNELUpdates: Future[SeqStateDate] = - for { - // push updated members count to inviteeUserId - _ ← seqUpdExt.deliverUserUpdate( - userId = cmd.inviteeUserId, - membersUpdateNew, - pushRules = seqUpdExt.pushRules(isFat = false, Some(PushTexts.Invited)), - deliveryId = s"invite_${groupId}_${cmd.randomId}" - ) - - // push all "refresh group" updates to inviteeUserId - _ ← FutureExt.ftraverse(inviteeUpdatesNew) { update ⇒ - seqUpdExt.deliverUserUpdate(userId = cmd.inviteeUserId, update) - } - - // push updated members count to all group members - SeqState(seq, state) ← seqUpdExt.broadcastClientUpdate( - userId = cmd.inviterUserId, - authId = cmd.inviterAuthId, - bcastUserIds = (memberIds - cmd.inviterUserId) - cmd.inviteeUserId, - update = membersUpdateNew, - deliveryId = s"useradded_${groupId}_${cmd.randomId}" - ) - - // push service message to invitee - _ ← seqUpdExt.deliverUserUpdate( - userId = cmd.inviteeUserId, - update = serviceMessageUpdate( - cmd.inviterUserId, - dateMillis, - cmd.randomId, - serviceMessage - ), - deliveryTag = Some(Optimization.GroupV2) - ) - } yield SeqStateDate(seq, state, dateMillis) - - val result: Future[SeqStateDate] = for { - /////////////////////////// - // Groups V1 API updates // - /////////////////////////// - - // push "Invited" to invitee - _ ← seqUpdExt.deliverUserUpdate( - userId = cmd.inviteeUserId, - inviteeUpdateObsolete, - pushRules = seqUpdExt.pushRules(isFat = true, Some(PushTexts.Invited)), - deliveryId = s"invite_obsolete_${groupId}_${cmd.randomId}" - ) - - // push "User added" to all group members except for `inviterUserId` - _ ← seqUpdExt.broadcastPeopleUpdate( - (memberIds - cmd.inviteeUserId) - cmd.inviterUserId, // is it right? - membersUpdateObsolete, - pushRules = seqUpdExt.pushRules(isFat = true, Some(PushTexts.Added)), - deliveryId = s"useradded_obsolete_${groupId}_${cmd.randomId}" - ) - - // push "User added" to `inviterUserId` - _ ← seqUpdExt.deliverClientUpdate( - cmd.inviterUserId, - cmd.inviterAuthId, - membersUpdateObsolete, - pushRules = seqUpdExt.pushRules(isFat = true, None), - deliveryId = s"useradded_obsolete_${groupId}_${cmd.randomId}" - ) - - /////////////////////////// - // Groups V2 API updates // - /////////////////////////// - - seqStateDate ← if (newState.groupType.isChannel) inviteCHANNELUpdates else inviteGROUPUpdates - - } yield seqStateDate - - result pipeTo sender() - } - } - } - - /** - * User can join - * • after invite(was invited by other user previously). In this case he already have group on devices - * • via invite link. In this case he doesn't have group, and we need to deliver it. - */ - protected def join(cmd: Join): Unit = { - // user is already a member, and should not complete invitation process - if (state.isMember(cmd.joiningUserId) && !state.isInvited(cmd.joiningUserId)) { - sender() ! Status.Failure(GroupErrors.UserAlreadyJoined) - } else { - // user was invited in group by other group user - val wasInvited = state.isInvited(cmd.joiningUserId) - - // trying to figure out who invited joining user. - // Descdending priority: - // • inviter defined in `Join` command (when invited via token) - // • inviter from members list (when invited by other user) - // • group creator (safe fallback) - val optMember = state.members.get(cmd.joiningUserId) - val inviterUserId = cmd.invitingUserId - .orElse(optMember.map(_.inviterUserId)) - .getOrElse(state.creatorUserId) - - persist(UserJoined(Instant.now, cmd.joiningUserId, inviterUserId)) { evt ⇒ - val newState = commit(evt) - - val date = evt.ts - val dateMillis = date.toEpochMilli - val memberIds = newState.memberIds - val apiMembers = newState.members.values.map(_.asStruct).toVector - val randomId = ACLUtils.randomLong() - - // If user was never invited to group - he don't have group on devices, - // that means we need to push all group-info related updates - // - // If user was invited to group by other member - we don't need to push group updates, - // cause they we pushed already on invite step - val joiningUserUpdatesNew: List[Update] = - if (wasInvited) List.empty[Update] else refreshGroupUpdates(newState, cmd.joiningUserId) - - val membersUpdateNew: Update = - if (newState.groupType.isChannel) // if channel, or group is big enough - UpdateGroupMembersCountChanged(groupId, newState.membersCount) - else - UpdateGroupMembersUpdated(groupId, apiMembers) // will update date when member got into group - - val membersUpdateObsolete = UpdateGroupMembersUpdateObsolete(groupId, apiMembers) - - val serviceMessage = GroupServiceMessages.userJoined - - //TODO: remove deprecated - db.run(GroupUserRepo.create( - groupId, - userId = cmd.joiningUserId, - inviterUserId = inviterUserId, - invitedAt = optMember.map(_.invitedAt).getOrElse(date), - joinedAt = Some(LocalDateTime.now(ZoneOffset.UTC)), - isAdmin = false - )) - - def joinGROUPUpdates: Future[SeqStateDate] = - for { - // push all group updates to joiningUserId - _ ← FutureExt.ftraverse(joiningUserUpdatesNew) { update ⇒ - seqUpdExt.deliverUserUpdate(userId = cmd.joiningUserId, update) - } - - // push updated members list to joining user, - // make it `FatSeqUpdate` if this user invited to group for first time. - // TODO???: isFat = !wasInvited - is it correct? - SeqState(seq, state) ← seqUpdExt.deliverClientUpdate( - userId = cmd.joiningUserId, - authId = cmd.joiningUserAuthId, - update = membersUpdateNew, - pushRules = seqUpdExt.pushRules(isFat = !wasInvited, None), //!wasInvited means that user came for first time here - deliveryId = s"join_${groupId}_${randomId}" - - ) - - // push updated members list to all group members except joiningUserId - _ ← seqUpdExt.broadcastPeopleUpdate( - memberIds - cmd.joiningUserId, - membersUpdateNew, - deliveryId = s"userjoined_${groupId}_${randomId}" - ) - - SeqStateDate(_, _, date) ← dialogExt.sendServerMessage( - apiGroupPeer, - senderUserId = cmd.joiningUserId, - senderAuthId = cmd.joiningUserAuthId, - randomId = randomId, - serviceMessage // no delivery tag. This updated handled this way in Groups V1 - ) - } yield SeqStateDate(seq, state, date) - - def joinCHANNELUpdates: Future[SeqStateDate] = - for { - // push all group updates to joiningUserId - _ ← FutureExt.ftraverse(joiningUserUpdatesNew) { update ⇒ - seqUpdExt.deliverUserUpdate(userId = cmd.joiningUserId, update) - } - - // push updated members count to joining user - SeqState(seq, state) ← seqUpdExt.deliverClientUpdate( - userId = cmd.joiningUserId, - authId = cmd.joiningUserAuthId, - update = membersUpdateNew, - deliveryId = s"join_${groupId}_${randomId}" - ) - - // push updated members count to all group members except joining user - _ ← seqUpdExt.broadcastPeopleUpdate( - memberIds - cmd.joiningUserId, - membersUpdateNew, - deliveryId = s"userjoined_${groupId}_${randomId}" - ) - } yield SeqStateDate(seq, state, dateMillis) - - val result: Future[(SeqStateDate, Vector[Int], Long)] = - for { - /////////////////////////// - // Groups V1 API updates // - /////////////////////////// - - // push update about members to all users, except joining user - _ ← seqUpdExt.broadcastPeopleUpdate( - memberIds - cmd.joiningUserId, - membersUpdateObsolete, - pushRules = seqUpdExt.pushRules(isFat = true, None), - deliveryId = s"userjoined_obsolete_${groupId}_${randomId}" - ) - - /////////////////////////// - // Groups V2 API updates // - /////////////////////////// - - seqStateDate ← if (newState.groupType.isChannel) joinCHANNELUpdates else joinGROUPUpdates - - } yield (seqStateDate, memberIds.toVector :+ inviterUserId, randomId) - - result pipeTo sender() - } - } - } - - /** - * This case handled in other manner, so we change state in the end - * cause user that left, should send service message. And we don't allow non-members - * to send message. So we keep him as member until message sent, and remove him from members - */ - protected def leave(cmd: Leave): Unit = { - if (state.nonMember(cmd.userId)) { - sender() ! notMember - } else { - persist(UserLeft(Instant.now, cmd.userId)) { evt ⇒ - // no commit here. it will be after service message sent - - val dateMillis = evt.ts.toEpochMilli - - val updateObsolete = UpdateGroupUserLeaveObsolete(groupId, cmd.userId, dateMillis, cmd.randomId) - - // TODO: merge, they are almost identical - val leftUserUpdatesNew = - if (state.groupType.isChannel) List( - UpdateGroupCanViewMembersChanged(groupId, canViewMembers = false), - UpdateGroupCanEditInfoChanged(groupId, canEditGroup = false), - UpdateGroupCanEditUsernameChanged(groupId, canEditUsername = false), - UpdateGroupCanEditAdminsChanged(groupId, canAssignAdmins = false), - UpdateGroupCanViewAdminsChanged(groupId, canViewAdmins = false), - UpdateGroupCanEditAdminSettingsChanged(groupId, canEditAdminSettings = false), - UpdateGroupCanInviteMembersChanged(groupId, canInviteMembers = false) - ) - else List( - UpdateGroupCanEditInfoChanged(groupId, canEditGroup = false), - UpdateGroupCanEditUsernameChanged(groupId, canEditUsername = false), - UpdateGroupCanEditAdminsChanged(groupId, canAssignAdmins = false), - UpdateGroupCanViewAdminsChanged(groupId, canViewAdmins = false), - UpdateGroupCanEditAdminSettingsChanged(groupId, canEditAdminSettings = false), - UpdateGroupCanViewMembersChanged(groupId, canViewMembers = false), - UpdateGroupMembersUpdated(groupId, members = Vector.empty), - UpdateGroupCanInviteMembersChanged(groupId, canInviteMembers = false) - ) - - val membersUpdateNew = - if (state.groupType.isChannel) { // if channel, or group is big enough - UpdateGroupMembersCountChanged( - groupId, - membersCount = state.membersCount - 1 - ) - } else { - UpdateGroupMembersUpdated( - groupId, - members = state.members.filterNot(_._1 == cmd.userId).values.map(_.asStruct).toVector - ) - } - - val serviceMessage = GroupServiceMessages.userLeft - - //TODO: remove deprecated. GroupInviteTokenRepo don't have replacement yet. - db.run( - for { - _ ← GroupUserRepo.delete(groupId, cmd.userId) - _ ← GroupInviteTokenRepo.revoke(groupId, cmd.userId) - } yield () - ) - - val leaveGROUPUpdates: Future[SeqStateDate] = - for { - // push updated members list to all group members - _ ← seqUpdExt.broadcastPeopleUpdate( - state.memberIds - cmd.userId, - membersUpdateNew - ) - - // send service message - SeqStateDate(_, _, date) ← dialogExt.sendServerMessage( - apiGroupPeer, - senderUserId = cmd.userId, - senderAuthId = cmd.authId, - randomId = cmd.randomId, - message = serviceMessage, - deliveryTag = Some(Optimization.GroupV2) - ) - - // push left user that he is no longer a member - SeqState(seq, state) ← seqUpdExt.deliverClientUpdate( - userId = cmd.userId, - authId = cmd.authId, - update = UpdateGroupMemberChanged(groupId, isMember = false) - ) - - // push left user updates - // • with empty group members - // • that he can't view and invite members - _ ← FutureExt.ftraverse(leftUserUpdatesNew) { update ⇒ - seqUpdExt.deliverUserUpdate(userId = cmd.userId, update) - } - } yield SeqStateDate(seq, state, date) - - val leaveCHANNELUpdates: Future[SeqStateDate] = - for { - // push updated members count to all group members - _ ← seqUpdExt.broadcastPeopleUpdate( - state.memberIds - cmd.userId, - membersUpdateNew - ) - - // push left user that he is no longer a member - SeqState(seq, state) ← seqUpdExt.deliverClientUpdate( - userId = cmd.userId, - authId = cmd.authId, - update = UpdateGroupMemberChanged(groupId, isMember = false) - ) - - // push left user updates that he has no group rights - _ ← FutureExt.ftraverse(leftUserUpdatesNew) { update ⇒ - seqUpdExt.deliverUserUpdate(userId = cmd.userId, update) - } - } yield SeqStateDate(seq, state, dateMillis) - - // read this dialog by user that leaves group. don't wait for ack - dialogExt.messageRead(apiGroupPeer, cmd.userId, 0L, dateMillis) - val result: Future[SeqStateDate] = for { - - /////////////////////////// - // Groups V1 API updates // - /////////////////////////// - - _ ← seqUpdExt.broadcastClientUpdate( - userId = cmd.userId, - authId = cmd.authId, - bcastUserIds = state.memberIds + cmd.userId, // push this to other user's devices too. actually cmd.userId is still in state.memberIds - update = updateObsolete, - pushRules = seqUpdExt.pushRules(isFat = false, Some(PushTexts.Left), Seq(cmd.authId)) - ) - - /////////////////////////// - // Groups V2 API updates // - /////////////////////////// - - seqStateDate ← if (state.groupType.isChannel) leaveCHANNELUpdates else leaveGROUPUpdates - - } yield seqStateDate - - result andThen { case _ ⇒ commit(evt) } pipeTo sender() - } - } - } - - protected def kick(cmd: Kick): Unit = { - if (!state.permissions.canKickMember(cmd.kickerUserId)) { - sender() ! noPermission - } else if (state.nonMember(cmd.kickedUserId)) { - sender() ! notMember - } else { - persist(UserKicked(Instant.now, cmd.kickedUserId, cmd.kickerUserId)) { evt ⇒ - val newState = commit(evt) - - val dateMillis = evt.ts.toEpochMilli - - val updateObsolete = UpdateGroupUserKickObsolete(groupId, cmd.kickedUserId, cmd.kickerUserId, dateMillis, cmd.randomId) - - // TODO: merge, they are almost identical - val kickedUserUpdatesNew: List[Update] = - if (state.groupType.isChannel) List( - UpdateGroupCanViewMembersChanged(groupId, canViewMembers = false), - UpdateGroupCanEditInfoChanged(groupId, canEditGroup = false), - UpdateGroupCanEditUsernameChanged(groupId, canEditUsername = false), - UpdateGroupCanEditAdminsChanged(groupId, canAssignAdmins = false), - UpdateGroupCanViewAdminsChanged(groupId, canViewAdmins = false), - UpdateGroupCanEditAdminSettingsChanged(groupId, canEditAdminSettings = false), - UpdateGroupCanInviteMembersChanged(groupId, canInviteMembers = false), - UpdateGroupMemberChanged(groupId, isMember = false) - ) - else List( - UpdateGroupCanViewMembersChanged(groupId, canViewMembers = false), - UpdateGroupCanEditInfoChanged(groupId, canEditGroup = false), - UpdateGroupCanEditUsernameChanged(groupId, canEditUsername = false), - UpdateGroupCanEditAdminsChanged(groupId, canAssignAdmins = false), - UpdateGroupCanViewAdminsChanged(groupId, canViewAdmins = false), - UpdateGroupCanEditAdminSettingsChanged(groupId, canEditAdminSettings = false), - UpdateGroupCanInviteMembersChanged(groupId, canInviteMembers = false), - UpdateGroupMembersUpdated(groupId, members = Vector.empty), - UpdateGroupMemberChanged(groupId, isMember = false) - ) - - val membersUpdateNew: Update = - if (newState.groupType.isChannel) { // if channel, or group is big enough - UpdateGroupMembersCountChanged( - groupId, - membersCount = newState.membersCount - ) - } else { - UpdateGroupMembersUpdated( - groupId, - members = newState.members.values.map(_.asStruct).toVector - ) - } - - val serviceMessage = GroupServiceMessages.userKicked(cmd.kickedUserId) - - //TODO: remove deprecated. GroupInviteTokenRepo don't have replacement yet. - db.run( - for { - _ ← GroupUserRepo.delete(groupId, cmd.kickedUserId) - _ ← GroupInviteTokenRepo.revoke(groupId, cmd.kickedUserId) - } yield () - ) - - val kickGROUPUpdates: Future[SeqStateDate] = - for { - // push updated members list to all group members. Don't push to kicked user! - SeqState(seq, state) ← seqUpdExt.broadcastClientUpdate( - userId = cmd.kickerUserId, - authId = cmd.kickerAuthId, - bcastUserIds = newState.memberIds - cmd.kickerUserId, - update = membersUpdateNew - ) - - SeqStateDate(_, _, date) ← dialogExt.sendServerMessage( - apiGroupPeer, - senderUserId = cmd.kickerUserId, - senderAuthId = cmd.kickerAuthId, - randomId = cmd.randomId, - message = serviceMessage, - deliveryTag = Some(Optimization.GroupV2) - ) - - // push kicked user updates - // • with empty group members - // • that he is no longer a member of group - // • that he can't view and invite members - _ ← FutureExt.ftraverse(kickedUserUpdatesNew) { update ⇒ - seqUpdExt.deliverUserUpdate(userId = cmd.kickedUserId, update) - } - } yield SeqStateDate(seq, state, date) - - val kickCHANNELUpdates: Future[SeqStateDate] = - for { - // push updated members count to all group members. Don't push to kicked user! - SeqState(seq, state) ← seqUpdExt.broadcastClientUpdate( - userId = cmd.kickerUserId, - authId = cmd.kickerAuthId, - bcastUserIds = newState.memberIds - cmd.kickerUserId, - update = membersUpdateNew - ) - - // push service message to kicker and kicked users. - _ ← seqUpdExt.broadcastPeopleUpdate( - userIds = Set(cmd.kickedUserId, cmd.kickerUserId), - update = serviceMessageUpdate( - cmd.kickerUserId, - dateMillis, - cmd.randomId, - serviceMessage - ), - deliveryTag = Some(Optimization.GroupV2) - ) - - // push kicked user updates that he has no group rights - _ ← FutureExt.ftraverse(kickedUserUpdatesNew) { update ⇒ - seqUpdExt.deliverUserUpdate(userId = cmd.kickedUserId, update) - } - } yield SeqStateDate(seq, state, dateMillis) - - // read this dialog by kicked user. don't wait for ack - dialogExt.messageRead(apiGroupPeer, cmd.kickedUserId, 0L, dateMillis) - val result: Future[SeqStateDate] = for { - /////////////////////////// - // Groups V1 API updates // - /////////////////////////// - - _ ← seqUpdExt.broadcastClientUpdate( - userId = cmd.kickerUserId, - authId = cmd.kickerAuthId, - bcastUserIds = newState.memberIds, - update = updateObsolete, - pushRules = seqUpdExt.pushRules(isFat = false, Some(PushTexts.Kicked), Seq(cmd.kickerAuthId)) - ) - - /////////////////////////// - // Groups V2 API updates // - /////////////////////////// - - seqStateDate ← if (state.groupType.isChannel) kickCHANNELUpdates else kickGROUPUpdates - - } yield seqStateDate - - result pipeTo sender() - } - } - } - - protected def updateAvatar(cmd: UpdateAvatar): Unit = { - if (!state.permissions.canEditInfo(cmd.clientUserId)) { - sender() ! noPermission - } else { - persist(AvatarUpdated(Instant.now, cmd.avatar)) { evt ⇒ - val newState = commit(evt) - - val dateMillis = evt.ts.toEpochMilli - val apiAvatar: Option[ApiAvatar] = cmd.avatar - val memberIds = newState.memberIds - - val updateNew = UpdateGroupAvatarChanged(groupId, apiAvatar) - val updateObsolete = UpdateGroupAvatarChangedObsolete(groupId, cmd.clientUserId, apiAvatar, dateMillis, cmd.randomId) - val serviceMessage = GroupServiceMessages.changedAvatar(apiAvatar) - - db.run(AvatarDataRepo.createOrUpdate(getAvatarData(cmd.avatar))) - val result: Future[UpdateAvatarAck] = for { - /////////////////////////// - // Groups V1 API updates // - /////////////////////////// - - _ ← seqUpdExt.broadcastClientUpdate(cmd.clientUserId, cmd.clientAuthId, memberIds, updateObsolete) - - /////////////////////////// - // Groups V2 API updates // - /////////////////////////// - - SeqState(seq, state) ← seqUpdExt.broadcastClientUpdate( - userId = cmd.clientUserId, - authId = cmd.clientAuthId, - bcastUserIds = memberIds - cmd.clientUserId, - update = updateNew - ) - SeqStateDate(_, _, date) ← dialogExt.sendServerMessage( - apiGroupPeer, - senderUserId = cmd.clientUserId, - senderAuthId = cmd.clientAuthId, - randomId = cmd.randomId, - message = serviceMessage, - deliveryTag = Some(Optimization.GroupV2) - ) - } yield UpdateAvatarAck(apiAvatar).withSeqStateDate(SeqStateDate(seq, state, date)) - - result pipeTo sender() - } - } - } - - protected def updateTitle(cmd: UpdateTitle): Unit = { - val title = cmd.title - if (!state.permissions.canEditInfo(cmd.clientUserId)) { - sender() ! noPermission - } else if (!isValidTitle(title)) { - sender() ! Status.Failure(InvalidTitle) - } else { - persist(TitleUpdated(Instant.now(), title)) { evt ⇒ - val newState = commit(evt) - - val dateMillis = evt.ts.toEpochMilli - val memberIds = newState.memberIds - - val updateNew = UpdateGroupTitleChanged(groupId, title) - val updateObsolete = UpdateGroupTitleChangedObsolete( - groupId, - userId = cmd.clientUserId, - title = title, - date = dateMillis, - randomId = cmd.randomId - ) - val serviceMessage = GroupServiceMessages.changedTitle(title) - val pushRules = seqUpdExt.pushRules(isFat = false, Some(PushTexts.TitleChanged)) - - //TODO: remove deprecated - db.run(GroupRepo.updateTitle(groupId, title, cmd.clientUserId, cmd.randomId, date = evt.ts)) - - val result: Future[SeqStateDate] = for { - - /////////////////////////// - // Groups V1 API updates // - /////////////////////////// - - _ ← seqUpdExt.broadcastClientUpdate( - cmd.clientUserId, - cmd.clientAuthId, - memberIds - cmd.clientUserId, - updateObsolete, - pushRules - ) - - /////////////////////////// - // Groups V2 API updates // - /////////////////////////// - - SeqState(seq, state) ← seqUpdExt.broadcastClientUpdate( - userId = cmd.clientUserId, - authId = cmd.clientAuthId, - bcastUserIds = memberIds - cmd.clientUserId, - update = updateNew, - pushRules = pushRules - ) - SeqStateDate(_, _, date) ← dialogExt.sendServerMessage( - apiGroupPeer, - senderUserId = cmd.clientUserId, - senderAuthId = cmd.clientAuthId, - randomId = cmd.randomId, - message = serviceMessage, - deliveryTag = Some(Optimization.GroupV2) - ) - } yield SeqStateDate(seq, state, date) - - result pipeTo sender() - } - - } - } - - //TODO: who can update topic??? - protected def updateTopic(cmd: UpdateTopic): Unit = { - def isValidTopic(topic: Option[String]) = topic.forall(_.length < 255) - - val topic = trimToEmpty(cmd.topic) - - if (state.groupType.isChannel && !state.isAdmin(cmd.clientUserId)) { - sender() ! notAdmin - } else if (state.nonMember(cmd.clientUserId)) { - sender() ! notMember - } else if (!isValidTopic(topic)) { - sender() ! Status.Failure(TopicTooLong) - } else { - persist(TopicUpdated(Instant.now, topic)) { evt ⇒ - val newState = commit(evt) - - val dateMillis = evt.ts.toEpochMilli - val memberIds = newState.memberIds - - val updateNew = UpdateGroupTopicChanged(groupId, topic) - val updateObsolete = UpdateGroupTopicChangedObsolete( - groupId, - randomId = cmd.randomId, - userId = cmd.clientUserId, - topic = topic, - date = dateMillis - ) - val serviceMessage = GroupServiceMessages.changedTopic(topic) - val pushRules = seqUpdExt.pushRules(isFat = false, Some(PushTexts.TopicChanged)) - - //TODO: remove deprecated - db.run(GroupRepo.updateTopic(groupId, topic)) - - val result: Future[SeqStateDate] = for { - - /////////////////////////// - // Groups V1 API updates // - /////////////////////////// - - _ ← seqUpdExt.broadcastClientUpdate( - cmd.clientUserId, - cmd.clientAuthId, - memberIds - cmd.clientUserId, - updateObsolete, - pushRules - ) - - /////////////////////////// - // Groups V2 API updates // - /////////////////////////// - - SeqState(seq, state) ← seqUpdExt.broadcastClientUpdate( - userId = cmd.clientUserId, - authId = cmd.clientAuthId, - bcastUserIds = memberIds - cmd.clientUserId, - update = updateNew, - pushRules = pushRules - ) - SeqStateDate(_, _, date) ← dialogExt.sendServerMessage( - apiGroupPeer, - senderUserId = cmd.clientUserId, - senderAuthId = cmd.clientAuthId, - randomId = cmd.randomId, - message = serviceMessage, - deliveryTag = Some(Optimization.GroupV2) - ) - } yield SeqStateDate(seq, state, date) - - result pipeTo sender() - } - } - } - - protected def updateAbout(cmd: UpdateAbout): Unit = { - def isValidAbout(about: Option[String]) = about.forall(_.length < 255) - - val about = trimToEmpty(cmd.about) - - if (!state.permissions.canEditInfo(cmd.clientUserId)) { - sender() ! noPermission - } else if (!isValidAbout(about)) { - sender() ! Status.Failure(AboutTooLong) - } else { - - persist(AboutUpdated(Instant.now, about)) { evt ⇒ - val newState = commit(evt) - - val memberIds = newState.memberIds - - val updateNew = UpdateGroupAboutChanged(groupId, about) - val updateObsolete = UpdateGroupAboutChangedObsolete(groupId, about) - val serviceMessage = GroupServiceMessages.changedAbout(about) - val pushRules = seqUpdExt.pushRules(isFat = false, Some(PushTexts.TopicChanged)) - - //TODO: remove deprecated - db.run(GroupRepo.updateAbout(groupId, about)) - - val result: Future[SeqStateDate] = for { - - /////////////////////////// - // Groups V1 API updates // - /////////////////////////// - - _ ← seqUpdExt.broadcastClientUpdate( - cmd.clientUserId, - cmd.clientAuthId, - memberIds - cmd.clientUserId, - updateObsolete, - pushRules - ) - - /////////////////////////// - // Groups V2 API updates // - /////////////////////////// - - SeqState(seq, state) ← seqUpdExt.broadcastClientUpdate( - userId = cmd.clientUserId, - authId = cmd.clientAuthId, - bcastUserIds = memberIds - cmd.clientUserId, - update = updateNew, - pushRules = pushRules - ) - } yield SeqStateDate(seq, state, evt.ts.toEpochMilli) - - result pipeTo sender() - } - } - } - - protected def updateShortName(cmd: UpdateShortName): Unit = { - def isValidShortName(shortName: Option[String]) = shortName forall StringUtils.validGlobalName - - val oldShortName = state.shortName - val newShortName = trimToEmpty(cmd.shortName) - - if (!state.permissions.canEditShortName(cmd.clientUserId)) { - sender() ! Status.Failure(NotOwner) - } else if (!isValidShortName(newShortName)) { - sender() ! Status.Failure(InvalidShortName) - } else if (oldShortName == newShortName) { - seqUpdExt.getSeqState(cmd.clientUserId, cmd.clientAuthId) pipeTo sender() - } else { - val replyTo = sender() - - val existsFu = newShortName map { name ⇒ - globalNamesStorage.exists(name) - } getOrElse FastFuture.successful(false) - - //TODO: timeout for this - onSuccess(existsFu) { exists ⇒ - if (exists) { - replyTo ! Status.Failure(ShortNameTaken) - } else { - persist(ShortNameUpdated(Instant.now, newShortName)) { evt ⇒ - val newState = commit(evt) - - val memberIds = newState.memberIds - - val result: Future[SeqState] = for { - _ ← globalNamesStorage.updateOrRemove( - oldShortName, - newShortName, - GlobalNameOwner(OwnerType.Group, groupId) - ) - seqState ← seqUpdExt.broadcastClientUpdate( - userId = cmd.clientUserId, - authId = cmd.clientAuthId, - bcastUserIds = memberIds - cmd.clientUserId, - update = UpdateGroupShortNameChanged(groupId, newShortName) - ) - } yield seqState - - result pipeTo replyTo - } - } - } - } - } - - protected def revokeIntegrationToken(cmd: RevokeIntegrationToken): Unit = { - if (!(state.isAdmin(cmd.clientUserId) || state.isOwner(cmd.clientUserId))) { - sender() ! notAdmin - } else { - val oldToken = state.bot.map(_.token) - val newToken = ACLUtils.accessToken() - - persist(IntegrationTokenRevoked(Instant.now, newToken)) { evt ⇒ - val newState = commit(evt) - - //TODO: remove deprecated - db.run(GroupBotRepo.updateToken(groupId, newToken)) - - val result: Future[RevokeIntegrationTokenAck] = for { - _ ← oldToken match { - case Some(token) ⇒ integrationStorage.deleteToken(token) - case None ⇒ FastFuture.successful(()) - } - _ ← integrationStorage.upsertToken(newToken, groupId) - } yield RevokeIntegrationTokenAck(newToken) - - result pipeTo sender() - } - } - } - - protected def makeUserAdmin(cmd: MakeUserAdmin): Unit = { - if (!state.permissions.canEditAdmins(cmd.clientUserId)) { - sender() ! noPermission - } else if (state.nonMember(cmd.candidateUserId)) { - sender() ! Status.Failure(NotAMember) - } else if (state.isAdmin(cmd.candidateUserId)) { - sender() ! Status.Failure(UserAlreadyAdmin) - } else { - persist(AdminStatusChanged(Instant.now, cmd.candidateUserId, isAdmin = true)) { evt ⇒ - val newState = commit(evt) - - val dateMillis = evt.ts.toEpochMilli - val memberIds = newState.memberIds - val members = newState.members.values.map(_.asStruct).toVector - - val updateAdmin = UpdateGroupMemberAdminChanged(groupId, cmd.candidateUserId, isAdmin = true) - val updateMembers = UpdateGroupMembersUpdated(groupId, members) - // now this user is admin, change edit rules for admins - val updateCanEdit = UpdateGroupCanEditInfoChanged(groupId, canEditGroup = newState.adminSettings.canAdminsEditGroupInfo) - - val updateObsolete = UpdateGroupMembersUpdateObsolete(groupId, members) - - //TODO: remove deprecated - db.run(GroupUserRepo.makeAdmin(groupId, cmd.candidateUserId)) - - val adminGROUPUpdates: Future[SeqStateDate] = - for { - _ ← seqUpdExt.broadcastPeopleUpdate( - userIds = memberIds + cmd.clientUserId, - updateAdmin - ) - SeqState(seq, state) ← seqUpdExt.broadcastClientUpdate( - cmd.clientUserId, - cmd.clientAuthId, - memberIds - cmd.clientUserId, - updateMembers - ) - } yield SeqStateDate(seq, state, dateMillis) - - val adminCHANNELUpdates: Future[SeqStateDate] = - for { - // push admin changed to all - _ ← seqUpdExt.broadcastPeopleUpdate( - userIds = memberIds + cmd.clientUserId, - updateAdmin - ) - // push changed members to admins and fresh admin - SeqState(seq, state) ← seqUpdExt.broadcastClientUpdate( - cmd.clientUserId, - cmd.clientAuthId, - (newState.adminIds - cmd.clientUserId) + cmd.candidateUserId, - updateMembers - ) - } yield SeqStateDate(seq, state, dateMillis) - - val result: Future[(Vector[ApiMember], SeqStateDate)] = for { - - /////////////////////////// - // Groups V1 API updates // - /////////////////////////// - - _ ← seqUpdExt.broadcastClientUpdate( - cmd.clientUserId, - cmd.clientAuthId, - memberIds - cmd.clientUserId, - updateObsolete - ) - - /////////////////////////// - // Groups V2 API updates // - /////////////////////////// - _ ← seqUpdExt.deliverUserUpdate( - userId = cmd.candidateUserId, - update = updateCanEdit - ) - seqStateDate ← if (state.groupType.isChannel) adminCHANNELUpdates else adminGROUPUpdates - - } yield (members, seqStateDate) - - result pipeTo sender() - } - } - } - - protected def dismissUserAdmin(cmd: DismissUserAdmin): Unit = { - if (!state.permissions.canEditAdmins(cmd.clientUserId)) { - sender() ! noPermission - } else if (state.nonMember(cmd.targetUserId)) { - sender() ! Status.Failure(NotAMember) - } else if (!state.isAdmin(cmd.targetUserId)) { - sender() ! Status.Failure(UserAlreadyNotAdmin) - } else { - persist(AdminStatusChanged(Instant.now, cmd.targetUserId, isAdmin = false)) { evt ⇒ - val newState = commit(evt) - - val dateMillis = evt.ts.toEpochMilli - val memberIds = newState.memberIds - val members = newState.members.values.map(_.asStruct).toVector - - val updateAdmin = UpdateGroupMemberAdminChanged(groupId, cmd.targetUserId, isAdmin = false) - val updateMembers = UpdateGroupMembersUpdated(groupId, members) - // now this user is not admin, change edit rules to plain members - val updateCanEdit = UpdateGroupCanEditInfoChanged(groupId, canEditGroup = newState.adminSettings.canMembersEditGroupInfo) - - val updateObsolete = UpdateGroupMembersUpdateObsolete(groupId, members) - - //TODO: remove deprecated - db.run(GroupUserRepo.dismissAdmin(groupId, cmd.targetUserId)) - - val adminGROUPUpdates: Future[SeqState] = - for { - // push admin changed to all - _ ← seqUpdExt.broadcastPeopleUpdate( - userIds = memberIds + cmd.clientUserId, - updateAdmin - ) - // push changed members to all users - seqState ← seqUpdExt.broadcastClientUpdate( - cmd.clientUserId, - cmd.clientAuthId, - memberIds - cmd.clientUserId, - updateMembers - ) - } yield seqState - - val adminCHANNELUpdates: Future[SeqState] = - for { - // push admin changed to all - _ ← seqUpdExt.broadcastPeopleUpdate( - userIds = memberIds + cmd.clientUserId, - updateAdmin - ) - // push changed members to admins and fresh admin - seqState ← seqUpdExt.broadcastClientUpdate( - cmd.clientUserId, - cmd.clientAuthId, - newState.adminIds - cmd.clientUserId, - updateMembers - ) - } yield seqState - - val result: Future[SeqState] = for { - - /////////////////////////// - // Groups V1 API updates // - /////////////////////////// - - _ ← seqUpdExt.broadcastClientUpdate( - cmd.clientUserId, - cmd.clientAuthId, - memberIds - cmd.clientUserId, - updateObsolete - ) - - /////////////////////////// - // Groups V2 API updates // - /////////////////////////// - _ ← seqUpdExt.deliverUserUpdate( - userId = cmd.targetUserId, - update = updateCanEdit - ) - seqState ← if (state.groupType.isChannel) adminCHANNELUpdates else adminGROUPUpdates - - } yield seqState - - result pipeTo sender() - } - } - } - - protected def transferOwnership(cmd: TransferOwnership): Unit = { - if (!state.isOwner(cmd.clientUserId)) { - sender() ! Status.Failure(CommonErrors.Forbidden) - } else { - persist(OwnerChanged(Instant.now, cmd.newOwnerId)) { evt ⇒ - val newState = commit(evt) - val memberIds = newState.memberIds - - val result: Future[SeqState] = for { - seqState ← seqUpdExt.broadcastClientUpdate( - userId = cmd.clientUserId, - authId = cmd.clientAuthId, - bcastUserIds = memberIds - cmd.clientUserId, - update = UpdateGroupOwnerChanged(groupId, cmd.newOwnerId), - pushRules = seqUpdExt.pushRules(isFat = false, None) - ) - } yield seqState - - result pipeTo sender() - } - } - } - - protected def updateAdminSettings(cmd: UpdateAdminSettings): Unit = { - if (!state.permissions.canEditAdminSettings(cmd.clientUserId)) { - sender() ! Status.Failure(NotAdmin) - } else { - val settOld = state.adminSettings - - persist(AdminSettingsUpdated(Instant.now, cmd.settingsBitMask)) { evt ⇒ - val newState = commit(evt) - val settNew = newState.adminSettings - - val (membersUpdates, adminsUpdates) = { - // push to all members except admins and owner - val showAdminToMembers = PartialFunction.condOpt(settOld.showAdminsToMembers != settNew.showAdminsToMembers) { - case true ⇒ UpdateGroupCanViewAdminsChanged(groupId, canViewAdmins = settNew.showAdminsToMembers) - } - - // push to all members except admins and owner - val canMembersInvite = PartialFunction.condOpt(settOld.canMembersInvite != settNew.canMembersInvite) { - case true ⇒ UpdateGroupCanInviteMembersChanged(groupId, canInviteMembers = settNew.canMembersInvite) - } - - // push to all members except admins and owner - val canMembersEditGroupInfo = PartialFunction.condOpt(settOld.canMembersEditGroupInfo != settNew.canMembersEditGroupInfo) { - case true ⇒ UpdateGroupCanEditInfoChanged(groupId, canEditGroup = settNew.canMembersEditGroupInfo) - } - - // push to admins only - val canAdminsEditGroupInfo = PartialFunction.condOpt(settOld.canAdminsEditGroupInfo != settNew.canAdminsEditGroupInfo) { - case true ⇒ UpdateGroupCanEditInfoChanged(groupId, canEditGroup = settNew.canAdminsEditGroupInfo) - } - - ( - List(showAdminToMembers, canMembersInvite, canMembersEditGroupInfo).flatten[Update], - List(canAdminsEditGroupInfo).flatten[Update] - ) - } - - val plainMemberIds = (newState.memberIds - newState.ownerUserId) -- newState.adminIds - val adminsOnlyIds = newState.adminIds - newState.ownerUserId - - val result: Future[UpdateAdminSettingsAck] = for { - // deliver updates about settings changed to plain group members - _ ← FutureExt.ftraverse(membersUpdates) { update ⇒ - seqUpdExt.broadcastPeopleUpdate(userIds = plainMemberIds, update) - } - // deliver updates about settings changed to plain group members - _ ← FutureExt.ftraverse(membersUpdates) { update ⇒ - seqUpdExt.broadcastPeopleUpdate(userIds = adminsOnlyIds, update) - } - } yield UpdateAdminSettingsAck() - - result pipeTo sender() - } - } - } - - private def serviceMessageUpdate(senderUserId: Int, date: Long, randomId: Long, message: ApiServiceMessage) = - UpdateMessage( - peer = apiGroupPeer, - senderUserId = senderUserId, - date = date, - randomId = randomId, - message = message, - attributes = None, - quotedMessage = None - ) - - private def trimToEmpty(s: Option[String]): Option[String] = - s map (_.trim) filter (_.nonEmpty) - - private def getAvatarData(avatar: Option[Avatar]): AvatarData = - avatar - .map(ImageUtils.getAvatarData(AvatarData.OfGroup, groupId, _)) - .getOrElse(AvatarData.empty(AvatarData.OfGroup, groupId.toLong)) - - private def isValidTitle(title: String) = title.nonEmpty && title.length < 255 - - // Updates that will be sent to user, when he enters group. - // Helps clients that have this group to refresh it's data. - // TODO: review when channels will be added - private def refreshGroupUpdates(newState: GroupState, userId: Int): List[Update] = List( - UpdateGroupMemberChanged(groupId, isMember = true), - UpdateGroupAboutChanged(groupId, newState.about), - UpdateGroupAvatarChanged(groupId, newState.avatar), - UpdateGroupTopicChanged(groupId, newState.topic), - UpdateGroupTitleChanged(groupId, newState.title), - UpdateGroupOwnerChanged(groupId, newState.ownerUserId), - UpdateGroupCanViewMembersChanged(groupId, canViewMembers = newState.permissions.canViewMembers(userId)), - UpdateGroupCanInviteMembersChanged(groupId, canInviteMembers = true) // TODO: figure out right value - // UpdateGroupExtChanged(groupId, newState.extension) //TODO: figure out and fix - // if(bigGroup) UpdateGroupMembersCountChanged(groupId, newState.extension) - ) + protected def isValidTitle(title: String) = title.nonEmpty && title.length < 255 } diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupOperations.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupOperations.scala index 627adf3aff..f9cca5f8c7 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupOperations.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupOperations.scala @@ -117,6 +117,11 @@ private[group] sealed trait Commands extends UserAcl { GroupEnvelope(groupId) .withUpdateAdminSettings(UpdateAdminSettings(clientUserId, AdminSettings.apiToBitMask(settings)))).mapTo[UpdateAdminSettingsAck] map (_ ⇒ ()) + def makeHistoryShared(groupId: Int, clientUserId: Int, clientAuthId: Long): Future[SeqState] = + (processorRegion.ref ? + GroupEnvelope(groupId) + .withMakeHistoryShared(MakeHistoryShared(clientUserId, clientAuthId))).mapTo[SeqState] + } private[group] sealed trait Queries { @@ -148,11 +153,6 @@ private[group] sealed trait Queries { GroupEnvelope(groupId) .withGetApiFullStruct(GetApiFullStruct(clientUserId))).mapTo[GetApiFullStructResponse] map (_.struct) - def isPublic(groupId: Int): Future[Boolean] = - (viewRegion.ref ? - GroupEnvelope(groupId) - .withIsPublic(IsPublic())).mapTo[IsPublicResponse] map (_.isPublic) - def isChannel(groupId: Int): Future[Boolean] = (viewRegion.ref ? GroupEnvelope(groupId) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala index 46dc0dea6a..fc7b92117d 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala @@ -11,7 +11,7 @@ import im.actor.serialization.ActorSerializer import im.actor.server.cqrs.{ Processor, TaggedEvent } import im.actor.server.db.DbExtension import im.actor.server.dialog.{ DialogEnvelope, DialogExtension } -import im.actor.server.group.GroupErrors.{ GroupIdAlreadyExists, GroupNotFound } +import im.actor.server.group.GroupErrors._ import im.actor.server.group.GroupCommands._ import im.actor.server.group.GroupQueries._ import im.actor.server.names.GlobalNamesStorageKeyValueStorage @@ -58,8 +58,6 @@ object GroupProcessor { 21006 → classOf[GroupQueries.GetMembersResponse], 21007 → classOf[GroupQueries.GetApiStruct], 21008 → classOf[GroupQueries.GetApiStructResponse], - 21009 → classOf[GroupQueries.IsPublic], - 21010 → classOf[GroupQueries.IsPublicResponse], 21012 → classOf[GroupQueries.GetAccessHash], 21013 → classOf[GroupQueries.GetAccessHashResponse], 21014 → classOf[GroupQueries.IsHistoryShared], @@ -145,6 +143,7 @@ private[group] final class GroupProcessor case d: DismissUserAdmin ⇒ dismissUserAdmin(d) case t: TransferOwnership ⇒ transferOwnership(t) case s: UpdateAdminSettings ⇒ updateAdminSettings(s) + case m: MakeHistoryShared ⇒ makeHistoryShared(m) // termination actions case StopProcessor ⇒ context stop self @@ -169,7 +168,6 @@ private[group] final class GroupProcessor case GetIntegrationToken(optClient) ⇒ getIntegrationToken(optClient) case GetMembers() ⇒ getMembers case LoadMembers(clientUserId, limit, offset) ⇒ loadMembers(clientUserId, limit, offset) - case IsPublic() ⇒ isPublic case IsChannel() ⇒ isChannel case IsHistoryShared() ⇒ isHistoryShared case GetApiStruct(clientUserId) ⇒ getApiStruct(clientUserId) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala index f92db7fa2e..534134b0e7 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala @@ -8,7 +8,7 @@ import com.google.protobuf.wrappers.Int32Value import im.actor.api.rpc.groups._ import im.actor.server.group.GroupErrors.{ NoPermission, NotOwner } import im.actor.server.group.GroupQueries._ -import im.actor.server.group.GroupType.{ Channel, General, Public } +import im.actor.server.group.GroupType.{ Channel, General, Unrecognized } import scala.concurrent.Future @@ -68,16 +68,13 @@ trait GroupQueryHandlers { } state.groupType match { - case General | Public ⇒ load + case General ⇒ load case Channel ⇒ if (state.isAdmin(clientUserId)) load else FastFuture.successful(LoadMembersResponse(Seq.empty, offsetBs)) } } - protected def isPublic = - FastFuture.successful(IsPublicResponse(state.groupType.isPublic)) - protected def isChannel = FastFuture.successful(IsChannelResponse(state.groupType.isChannel)) @@ -108,8 +105,8 @@ trait GroupQueryHandlers { ext = None, membersCount = Some(count), groupType = Some(state.groupType match { - case GroupType.Channel ⇒ ApiGroupType.CHANNEL - case GroupType.General | GroupType.Public | GroupType.Unrecognized(_) ⇒ ApiGroupType.GROUP + case Channel ⇒ ApiGroupType.CHANNEL + case General | Unrecognized(_) ⇒ ApiGroupType.GROUP }), canSendMessage = Some(state.permissions.canSendMessage(clientUserId)) ) @@ -150,8 +147,8 @@ trait GroupQueryHandlers { FastFuture.successful { val canSend = state.bot.exists(_.userId == clientUserId) || { state.groupType match { - case General | Public ⇒ state.isMember(clientUserId) - case Channel ⇒ state.isAdmin(clientUserId) + case General ⇒ state.isMember(clientUserId) + case Channel ⇒ state.isAdmin(clientUserId) } } CanSendMessageResponse( @@ -196,7 +193,7 @@ trait GroupQueryHandlers { if (state.isMember(clientUserId)) { state.groupType match { - case General | Public ⇒ + case General ⇒ apiMembers → group.membersCount case Channel ⇒ if (state.isAdmin(clientUserId)) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala index 3404476df4..80b6fb0da0 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala @@ -8,7 +8,7 @@ import im.actor.api.rpc.misc.ApiExtension import im.actor.server.cqrs.{ Event, ProcessorState } import im.actor.server.file.Avatar import im.actor.server.group.GroupEvents._ -import im.actor.server.group.GroupType.{ Channel, General, Public } +import im.actor.server.group.GroupType.{ Channel, General } private[group] final case class Member( userId: Int, @@ -23,9 +23,16 @@ private[group] final case class Bot( ) object AdminSettings { - val Default = AdminSettings( + val PlainDefault = AdminSettings( showAdminsToMembers = true, canMembersInvite = true, + canMembersEditGroupInfo = true, + canAdminsEditGroupInfo = true + ) + + val ChannelsDefault = AdminSettings( + showAdminsToMembers = false, + canMembersInvite = false, canMembersEditGroupInfo = false, canAdminsEditGroupInfo = true ) @@ -79,7 +86,7 @@ private[group] object GroupState { members = Map.empty, invitedUserIds = Set.empty, accessHash = 0L, - adminSettings = AdminSettings.Default, + adminSettings = AdminSettings.PlainDefault, bot = None, //??? @@ -141,16 +148,17 @@ private[group] final case class GroupState( val isCreated = createdAt.nonEmpty + //TODO: add on commit(not during recovery!) hook to make group with async members, when more than 100 def isAsyncMembers = groupType match { - case General | Public ⇒ members.size > 100 - case Channel ⇒ true + case General ⇒ members.size > 100 + case Channel ⇒ true } def getShowableOwner(clientUserId: Int): Option[Int] = groupType match { - case General | Public ⇒ Some(creatorUserId) - case Channel ⇒ if (isAdmin(clientUserId)) Some(creatorUserId) else None + case General ⇒ Some(creatorUserId) + case Channel ⇒ if (isAdmin(clientUserId)) Some(creatorUserId) else None } override def updated(e: Event): GroupState = e match { @@ -164,6 +172,7 @@ private[group] final case class GroupState( about = None, avatar = None, topic = None, + shortName = None, groupType = evt.typ.getOrElse(GroupType.General), isHidden = evt.isHidden getOrElse false, isHistoryShared = evt.isHistoryShared getOrElse false, @@ -180,6 +189,9 @@ private[group] final case class GroupState( ).toMap, invitedUserIds = evt.userIds.filterNot(_ == evt.creatorUserId).toSet, accessHash = evt.accessHash, + adminSettings = + if (groupType.isChannel) AdminSettings.ChannelsDefault + else AdminSettings.PlainDefault, bot = None, extensions = (evt.extensions map { //TODO: validate is it right? case ApiExtension(extId, data) ⇒ @@ -230,11 +242,6 @@ private[group] final case class GroupState( this.copy(avatar = newAvatar) case TitleUpdated(_, newTitle) ⇒ this.copy(title = newTitle) - case BecamePublic(_) ⇒ - this.copy( - groupType = GroupType.Public, - isHistoryShared = true - ) case AboutUpdated(_, newAbout) ⇒ this.copy(about = newAbout) case TopicUpdated(_, newTopic) ⇒ @@ -253,12 +260,16 @@ private[group] final case class GroupState( this.copy(shortName = newShortName) case AdminSettingsUpdated(_, bitMask) ⇒ this.copy(adminSettings = AdminSettings.fromBitMask(bitMask)) + case HistoryBecameShared(_, _) ⇒ + this.copy(isHistoryShared = true) - // deprecated event + // deprecated events case UserBecameAdmin(_, userId, _) ⇒ this.copy( members = members.updated(userId, members(userId).copy(isAdmin = true)) ) + case BecamePublic(_) ⇒ + this.copy(isHistoryShared = true) } // TODO: real snapshot @@ -274,8 +285,8 @@ private[group] final case class GroupState( def canSendMessage(clientUserId: Int) = { groupType match { - case General | Public ⇒ isMember(clientUserId) - case Channel ⇒ isAdmin(clientUserId) || isOwner(clientUserId) + case General ⇒ isMember(clientUserId) + case Channel ⇒ isAdmin(clientUserId) || isOwner(clientUserId) } } || bot.exists(_.userId == clientUserId) @@ -285,8 +296,8 @@ private[group] final case class GroupState( */ def canViewMembers(clientUserId: Int) = groupType match { - case General | Public ⇒ isMember(clientUserId) - case Channel ⇒ isAdmin(clientUserId) || isOwner(clientUserId) + case General ⇒ isMember(clientUserId) + case Channel ⇒ isAdmin(clientUserId) || isOwner(clientUserId) } /** @@ -317,6 +328,9 @@ private[group] final case class GroupState( // only owner can change short name def canEditShortName(clientUserId: Int): Boolean = isOwner(clientUserId) + // only owner can make history shared + def canMakeHistoryShared(clientUserId: Int): Boolean = isOwner(clientUserId) + // only owner and other admins can edit admins list def canEditAdmins(clientUserId: Int): Boolean = isOwner(clientUserId) || isAdmin(clientUserId) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/InfoCommandHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/InfoCommandHandlers.scala new file mode 100644 index 0000000000..7072283b75 --- /dev/null +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/InfoCommandHandlers.scala @@ -0,0 +1,337 @@ +package im.actor.server.group + +import java.time.Instant + +import akka.actor.Status +import akka.pattern.pipe +import akka.http.scaladsl.util.FastFuture +import im.actor.api.rpc.files.ApiAvatar +import im.actor.api.rpc.groups._ +import im.actor.server.file.{ Avatar, ImageUtils } +import im.actor.server.group.GroupCommands.{ MakeHistoryShared, UpdateAbout, UpdateAvatar, UpdateAvatarAck, UpdateShortName, UpdateTitle, UpdateTopic } +import im.actor.server.group.GroupErrors._ +import im.actor.server.group.GroupEvents.{ AboutUpdated, AvatarUpdated, ShortNameUpdated, TitleUpdated, TopicUpdated } +import im.actor.server.model.AvatarData +import im.actor.server.names.{ GlobalNameOwner, OwnerType } +import im.actor.server.persist.{ AvatarDataRepo, GroupRepo } +import im.actor.server.sequence.{ Optimization, SeqState, SeqStateDate } +import im.actor.util.misc.StringUtils + +import scala.concurrent.Future + +private[group] trait InfoCommandHandlers { + this: GroupProcessor ⇒ + + import im.actor.server.ApiConversions._ + + protected def updateAvatar(cmd: UpdateAvatar): Unit = { + if (!state.permissions.canEditInfo(cmd.clientUserId)) { + sender() ! noPermission + } else { + persist(AvatarUpdated(Instant.now, cmd.avatar)) { evt ⇒ + val newState = commit(evt) + + val dateMillis = evt.ts.toEpochMilli + val apiAvatar: Option[ApiAvatar] = cmd.avatar + val memberIds = newState.memberIds + + val updateNew = UpdateGroupAvatarChanged(groupId, apiAvatar) + val updateObsolete = UpdateGroupAvatarChangedObsolete(groupId, cmd.clientUserId, apiAvatar, dateMillis, cmd.randomId) + val serviceMessage = GroupServiceMessages.changedAvatar(apiAvatar) + + db.run(AvatarDataRepo.createOrUpdate(getAvatarData(cmd.avatar))) + val result: Future[UpdateAvatarAck] = for { + /////////////////////////// + // Groups V1 API updates // + /////////////////////////// + + _ ← seqUpdExt.broadcastClientUpdate(cmd.clientUserId, cmd.clientAuthId, memberIds, updateObsolete) + + /////////////////////////// + // Groups V2 API updates // + /////////////////////////// + + SeqState(seq, state) ← seqUpdExt.broadcastClientUpdate( + userId = cmd.clientUserId, + authId = cmd.clientAuthId, + bcastUserIds = memberIds - cmd.clientUserId, + update = updateNew + ) + SeqStateDate(_, _, date) ← dialogExt.sendServerMessage( + apiGroupPeer, + senderUserId = cmd.clientUserId, + senderAuthId = cmd.clientAuthId, + randomId = cmd.randomId, + message = serviceMessage, + deliveryTag = Some(Optimization.GroupV2) + ) + } yield UpdateAvatarAck(apiAvatar).withSeqStateDate(SeqStateDate(seq, state, date)) + + result pipeTo sender() + } + } + } + + protected def updateTitle(cmd: UpdateTitle): Unit = { + val title = cmd.title + if (!state.permissions.canEditInfo(cmd.clientUserId)) { + sender() ! noPermission + } else if (!isValidTitle(title)) { + sender() ! Status.Failure(InvalidTitle) + } else { + persist(TitleUpdated(Instant.now(), title)) { evt ⇒ + val newState = commit(evt) + + val dateMillis = evt.ts.toEpochMilli + val memberIds = newState.memberIds + + val updateNew = UpdateGroupTitleChanged(groupId, title) + val updateObsolete = UpdateGroupTitleChangedObsolete( + groupId, + userId = cmd.clientUserId, + title = title, + date = dateMillis, + randomId = cmd.randomId + ) + val serviceMessage = GroupServiceMessages.changedTitle(title) + val pushRules = seqUpdExt.pushRules(isFat = false, Some(PushTexts.TitleChanged)) + + //TODO: remove deprecated + db.run(GroupRepo.updateTitle(groupId, title, cmd.clientUserId, cmd.randomId, date = evt.ts)) + + val result: Future[SeqStateDate] = for { + + /////////////////////////// + // Groups V1 API updates // + /////////////////////////// + + _ ← seqUpdExt.broadcastClientUpdate( + cmd.clientUserId, + cmd.clientAuthId, + memberIds - cmd.clientUserId, + updateObsolete, + pushRules + ) + + /////////////////////////// + // Groups V2 API updates // + /////////////////////////// + + SeqState(seq, state) ← seqUpdExt.broadcastClientUpdate( + userId = cmd.clientUserId, + authId = cmd.clientAuthId, + bcastUserIds = memberIds - cmd.clientUserId, + update = updateNew, + pushRules = pushRules + ) + SeqStateDate(_, _, date) ← dialogExt.sendServerMessage( + apiGroupPeer, + senderUserId = cmd.clientUserId, + senderAuthId = cmd.clientAuthId, + randomId = cmd.randomId, + message = serviceMessage, + deliveryTag = Some(Optimization.GroupV2) + ) + } yield SeqStateDate(seq, state, date) + + result pipeTo sender() + } + + } + } + + //TODO: who can update topic??? + protected def updateTopic(cmd: UpdateTopic): Unit = { + def isValidTopic(topic: Option[String]) = topic.forall(_.length < 255) + + val topic = trimToEmpty(cmd.topic) + + if (state.groupType.isChannel && !state.isAdmin(cmd.clientUserId)) { + sender() ! notAdmin + } else if (state.nonMember(cmd.clientUserId)) { + sender() ! notMember + } else if (!isValidTopic(topic)) { + sender() ! Status.Failure(TopicTooLong) + } else { + persist(TopicUpdated(Instant.now, topic)) { evt ⇒ + val newState = commit(evt) + + val dateMillis = evt.ts.toEpochMilli + val memberIds = newState.memberIds + + val updateNew = UpdateGroupTopicChanged(groupId, topic) + val updateObsolete = UpdateGroupTopicChangedObsolete( + groupId, + randomId = cmd.randomId, + userId = cmd.clientUserId, + topic = topic, + date = dateMillis + ) + val serviceMessage = GroupServiceMessages.changedTopic(topic) + val pushRules = seqUpdExt.pushRules(isFat = false, Some(PushTexts.TopicChanged)) + + //TODO: remove deprecated + db.run(GroupRepo.updateTopic(groupId, topic)) + + val result: Future[SeqStateDate] = for { + + /////////////////////////// + // Groups V1 API updates // + /////////////////////////// + + _ ← seqUpdExt.broadcastClientUpdate( + cmd.clientUserId, + cmd.clientAuthId, + memberIds - cmd.clientUserId, + updateObsolete, + pushRules + ) + + /////////////////////////// + // Groups V2 API updates // + /////////////////////////// + + SeqState(seq, state) ← seqUpdExt.broadcastClientUpdate( + userId = cmd.clientUserId, + authId = cmd.clientAuthId, + bcastUserIds = memberIds - cmd.clientUserId, + update = updateNew, + pushRules = pushRules + ) + SeqStateDate(_, _, date) ← dialogExt.sendServerMessage( + apiGroupPeer, + senderUserId = cmd.clientUserId, + senderAuthId = cmd.clientAuthId, + randomId = cmd.randomId, + message = serviceMessage, + deliveryTag = Some(Optimization.GroupV2) + ) + } yield SeqStateDate(seq, state, date) + + result pipeTo sender() + } + } + } + + protected def updateAbout(cmd: UpdateAbout): Unit = { + def isValidAbout(about: Option[String]) = about.forall(_.length < 255) + + val about = trimToEmpty(cmd.about) + + if (!state.permissions.canEditInfo(cmd.clientUserId)) { + sender() ! noPermission + } else if (!isValidAbout(about)) { + sender() ! Status.Failure(AboutTooLong) + } else { + + persist(AboutUpdated(Instant.now, about)) { evt ⇒ + val newState = commit(evt) + + val memberIds = newState.memberIds + + val updateNew = UpdateGroupAboutChanged(groupId, about) + val updateObsolete = UpdateGroupAboutChangedObsolete(groupId, about) + val serviceMessage = GroupServiceMessages.changedAbout(about) + val pushRules = seqUpdExt.pushRules(isFat = false, Some(PushTexts.TopicChanged)) + + //TODO: remove deprecated + db.run(GroupRepo.updateAbout(groupId, about)) + + val result: Future[SeqStateDate] = for { + + /////////////////////////// + // Groups V1 API updates // + /////////////////////////// + + _ ← seqUpdExt.broadcastClientUpdate( + cmd.clientUserId, + cmd.clientAuthId, + memberIds - cmd.clientUserId, + updateObsolete, + pushRules + ) + + /////////////////////////// + // Groups V2 API updates // + /////////////////////////// + + SeqState(seq, state) ← seqUpdExt.broadcastClientUpdate( + userId = cmd.clientUserId, + authId = cmd.clientAuthId, + bcastUserIds = memberIds - cmd.clientUserId, + update = updateNew, + pushRules = pushRules + ) + } yield SeqStateDate(seq, state, evt.ts.toEpochMilli) + + result pipeTo sender() + } + } + } + + protected def updateShortName(cmd: UpdateShortName): Unit = { + def isValidShortName(shortName: Option[String]) = shortName forall StringUtils.validGlobalName + + val oldShortName = state.shortName + val newShortName = trimToEmpty(cmd.shortName) + + if (!state.permissions.canEditShortName(cmd.clientUserId)) { + sender() ! noPermission + } else if (!isValidShortName(newShortName)) { + sender() ! Status.Failure(InvalidShortName) + } else if (oldShortName == newShortName) { + seqUpdExt.getSeqState(cmd.clientUserId, cmd.clientAuthId) pipeTo sender() + } else { + val replyTo = sender() + + val existsFu = newShortName map { name ⇒ + globalNamesStorage.exists(name) + } getOrElse FastFuture.successful(false) + + //TODO: timeout for this + onSuccess(existsFu) { exists ⇒ + if (exists) { + replyTo ! Status.Failure(ShortNameTaken) + } else { + // when user sets short name first time - we making group history shared + if (state.shortName.isEmpty && newShortName.nonEmpty && !state.isHistoryShared) { + context.parent ! + GroupEnvelope(groupId) + .withMakeHistoryShared(MakeHistoryShared(cmd.clientUserId, cmd.clientAuthId)) + } + + persist(ShortNameUpdated(Instant.now, newShortName)) { evt ⇒ + val newState = commit(evt) + + val memberIds = newState.memberIds + + val result: Future[SeqState] = for { + _ ← globalNamesStorage.updateOrRemove( + oldShortName, + newShortName, + GlobalNameOwner(OwnerType.Group, groupId) + ) + seqState ← seqUpdExt.broadcastClientUpdate( + userId = cmd.clientUserId, + authId = cmd.clientAuthId, + bcastUserIds = memberIds - cmd.clientUserId, + update = UpdateGroupShortNameChanged(groupId, newShortName) + ) + } yield seqState + + result pipeTo replyTo + } + } + } + } + } + + private def getAvatarData(avatar: Option[Avatar]): AvatarData = + avatar + .map(ImageUtils.getAvatarData(AvatarData.OfGroup, groupId, _)) + .getOrElse(AvatarData.empty(AvatarData.OfGroup, groupId.toLong)) + + private def trimToEmpty(s: Option[String]): Option[String] = + s map (_.trim) filter (_.nonEmpty) + +} diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/MemberCommandHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/MemberCommandHandlers.scala new file mode 100644 index 0000000000..3eeecb9cd0 --- /dev/null +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/MemberCommandHandlers.scala @@ -0,0 +1,645 @@ +package im.actor.server.group + +import java.time.{ Instant, LocalDateTime, ZoneOffset } + +import akka.actor.Status +import akka.pattern.pipe +import im.actor.api.rpc.Update +import im.actor.api.rpc.groups._ +import im.actor.api.rpc.messaging.{ ApiServiceMessage, UpdateMessage } +import im.actor.concurrent.FutureExt +import im.actor.server.acl.ACLUtils +import im.actor.server.group.GroupCommands.{ Invite, Join, Kick, Leave } +import im.actor.server.group.GroupEvents.{ UserInvited, UserJoined, UserKicked, UserLeft } +import im.actor.server.persist.{ GroupInviteTokenRepo, GroupUserRepo } +import im.actor.server.sequence.{ Optimization, SeqState, SeqStateDate } + +import scala.concurrent.Future + +private[group] trait MemberCommandHandlers extends GroupsImplicits { + this: GroupProcessor ⇒ + + import im.actor.server.ApiConversions._ + + protected def invite(cmd: Invite): Unit = { + if (!state.permissions.canInvitePeople(cmd.inviterUserId)) { + sender() ! noPermission + } else if (state.isInvited(cmd.inviteeUserId)) { + sender() ! Status.Failure(GroupErrors.UserAlreadyInvited) + } else if (state.isMember(cmd.inviteeUserId)) { + sender() ! Status.Failure(GroupErrors.UserAlreadyJoined) + } else { + val inviteeIsExUser = state.isExUser(cmd.inviteeUserId) + + persist(UserInvited(Instant.now, cmd.inviteeUserId, cmd.inviterUserId)) { evt ⇒ + val newState = commit(evt) + + val dateMillis = evt.ts.toEpochMilli + val memberIds = newState.memberIds + val apiMembers = newState.members.values.map(_.asStruct).toVector + + // if user ever been in this group - we should push these updates, + val inviteeUpdatesNew: List[Update] = refreshGroupUpdates(newState, cmd.inviteeUserId) + + val membersUpdateNew: Update = + if (newState.groupType.isChannel) // if channel, or group is big enough + UpdateGroupMembersCountChanged(groupId, newState.membersCount) + else + UpdateGroupMembersUpdated(groupId, apiMembers) + + val inviteeUpdateObsolete = UpdateGroupInviteObsolete( + groupId, + inviteUserId = cmd.inviterUserId, + date = dateMillis, + randomId = cmd.randomId + ) + + val membersUpdateObsolete = UpdateGroupUserInvitedObsolete( + groupId, + userId = cmd.inviteeUserId, + inviterUserId = cmd.inviterUserId, + date = dateMillis, + randomId = cmd.randomId + ) + val serviceMessage = GroupServiceMessages.userInvited(cmd.inviteeUserId) + + //TODO: remove deprecated + db.run(GroupUserRepo.create(groupId, cmd.inviteeUserId, cmd.inviterUserId, evt.ts, None, isAdmin = false)) + + def inviteGROUPUpdates: Future[SeqStateDate] = + for { + // push updated members list to inviteeUserId, + // make it `FatSeqUpdate` if this user invited to group for first time. + _ ← seqUpdExt.deliverUserUpdate( + userId = cmd.inviteeUserId, + membersUpdateNew, + pushRules = seqUpdExt.pushRules(isFat = !inviteeIsExUser, Some(PushTexts.Invited)), + deliveryId = s"invite_${groupId}_${cmd.randomId}" + ) + + // push all "refresh group" updates to inviteeUserId + _ ← FutureExt.ftraverse(inviteeUpdatesNew) { update ⇒ + seqUpdExt.deliverUserUpdate(userId = cmd.inviteeUserId, update) + } + + // push updated members list to all group members except inviteeUserId + SeqState(seq, state) ← seqUpdExt.broadcastClientUpdate( + userId = cmd.inviterUserId, + authId = cmd.inviterAuthId, + bcastUserIds = (memberIds - cmd.inviterUserId) - cmd.inviteeUserId, + update = membersUpdateNew, + deliveryId = s"useradded_${groupId}_${cmd.randomId}" + ) + + // explicitly send service message + SeqStateDate(_, _, date) ← dialogExt.sendServerMessage( + apiGroupPeer, + cmd.inviterUserId, + cmd.inviterAuthId, + cmd.randomId, + serviceMessage, + deliveryTag = Some(Optimization.GroupV2) + ) + } yield SeqStateDate(seq, state, date) + + def inviteCHANNELUpdates: Future[SeqStateDate] = + for { + // push updated members count to inviteeUserId + _ ← seqUpdExt.deliverUserUpdate( + userId = cmd.inviteeUserId, + membersUpdateNew, + pushRules = seqUpdExt.pushRules(isFat = false, Some(PushTexts.Invited)), + deliveryId = s"invite_${groupId}_${cmd.randomId}" + ) + + // push all "refresh group" updates to inviteeUserId + _ ← FutureExt.ftraverse(inviteeUpdatesNew) { update ⇒ + seqUpdExt.deliverUserUpdate(userId = cmd.inviteeUserId, update) + } + + // push updated members count to all group members + SeqState(seq, state) ← seqUpdExt.broadcastClientUpdate( + userId = cmd.inviterUserId, + authId = cmd.inviterAuthId, + bcastUserIds = (memberIds - cmd.inviterUserId) - cmd.inviteeUserId, + update = membersUpdateNew, + deliveryId = s"useradded_${groupId}_${cmd.randomId}" + ) + + // push service message to invitee + _ ← seqUpdExt.deliverUserUpdate( + userId = cmd.inviteeUserId, + update = serviceMessageUpdate( + cmd.inviterUserId, + dateMillis, + cmd.randomId, + serviceMessage + ), + deliveryTag = Some(Optimization.GroupV2) + ) + } yield SeqStateDate(seq, state, dateMillis) + + val result: Future[SeqStateDate] = for { + /////////////////////////// + // Groups V1 API updates // + /////////////////////////// + + // push "Invited" to invitee + _ ← seqUpdExt.deliverUserUpdate( + userId = cmd.inviteeUserId, + inviteeUpdateObsolete, + pushRules = seqUpdExt.pushRules(isFat = true, Some(PushTexts.Invited)), + deliveryId = s"invite_obsolete_${groupId}_${cmd.randomId}" + ) + + // push "User added" to all group members except for `inviterUserId` + _ ← seqUpdExt.broadcastPeopleUpdate( + (memberIds - cmd.inviteeUserId) - cmd.inviterUserId, // is it right? + membersUpdateObsolete, + pushRules = seqUpdExt.pushRules(isFat = true, Some(PushTexts.Added)), + deliveryId = s"useradded_obsolete_${groupId}_${cmd.randomId}" + ) + + // push "User added" to `inviterUserId` + _ ← seqUpdExt.deliverClientUpdate( + cmd.inviterUserId, + cmd.inviterAuthId, + membersUpdateObsolete, + pushRules = seqUpdExt.pushRules(isFat = true, None), + deliveryId = s"useradded_obsolete_${groupId}_${cmd.randomId}" + ) + + /////////////////////////// + // Groups V2 API updates // + /////////////////////////// + + seqStateDate ← if (newState.groupType.isChannel) inviteCHANNELUpdates else inviteGROUPUpdates + + } yield seqStateDate + + result pipeTo sender() + } + } + } + + /** + * User can join + * • after invite(was invited by other user previously). In this case he already have group on devices + * • via invite link. In this case he doesn't have group, and we need to deliver it. + */ + protected def join(cmd: Join): Unit = { + // user is already a member, and should not complete invitation process + if (state.isMember(cmd.joiningUserId) && !state.isInvited(cmd.joiningUserId)) { + sender() ! Status.Failure(GroupErrors.UserAlreadyJoined) + } else { + // user was invited in group by other group user + val wasInvited = state.isInvited(cmd.joiningUserId) + + // trying to figure out who invited joining user. + // Descdending priority: + // • inviter defined in `Join` command (when invited via token) + // • inviter from members list (when invited by other user) + // • group creator (safe fallback) + val optMember = state.members.get(cmd.joiningUserId) + val inviterUserId = cmd.invitingUserId + .orElse(optMember.map(_.inviterUserId)) + .getOrElse(state.ownerUserId) + + persist(UserJoined(Instant.now, cmd.joiningUserId, inviterUserId)) { evt ⇒ + val newState = commit(evt) + + val date = evt.ts + val dateMillis = date.toEpochMilli + val memberIds = newState.memberIds + val apiMembers = newState.members.values.map(_.asStruct).toVector + val randomId = ACLUtils.randomLong() + + // If user was never invited to group - he don't have group on devices, + // that means we need to push all group-info related updates + // + // If user was invited to group by other member - we don't need to push group updates, + // cause they we pushed already on invite step + val joiningUserUpdatesNew: List[Update] = + if (wasInvited) List.empty[Update] else refreshGroupUpdates(newState, cmd.joiningUserId) + + val membersUpdateNew: Update = + if (newState.groupType.isChannel) // if channel, or group is big enough + UpdateGroupMembersCountChanged(groupId, newState.membersCount) + else + UpdateGroupMembersUpdated(groupId, apiMembers) // will update date when member got into group + + val membersUpdateObsolete = UpdateGroupMembersUpdateObsolete(groupId, apiMembers) + + val serviceMessage = GroupServiceMessages.userJoined + + //TODO: remove deprecated + db.run(GroupUserRepo.create( + groupId, + userId = cmd.joiningUserId, + inviterUserId = inviterUserId, + invitedAt = optMember.map(_.invitedAt).getOrElse(date), + joinedAt = Some(LocalDateTime.now(ZoneOffset.UTC)), + isAdmin = false + )) + + def joinGROUPUpdates: Future[SeqStateDate] = + for { + // push all group updates to joiningUserId + _ ← FutureExt.ftraverse(joiningUserUpdatesNew) { update ⇒ + seqUpdExt.deliverUserUpdate(userId = cmd.joiningUserId, update) + } + + // push updated members list to joining user, + // make it `FatSeqUpdate` if this user invited to group for first time. + // TODO???: isFat = !wasInvited - is it correct? + SeqState(seq, state) ← seqUpdExt.deliverClientUpdate( + userId = cmd.joiningUserId, + authId = cmd.joiningUserAuthId, + update = membersUpdateNew, + pushRules = seqUpdExt.pushRules(isFat = !wasInvited, None), //!wasInvited means that user came for first time here + deliveryId = s"join_${groupId}_${randomId}" + + ) + + // push updated members list to all group members except joiningUserId + _ ← seqUpdExt.broadcastPeopleUpdate( + memberIds - cmd.joiningUserId, + membersUpdateNew, + deliveryId = s"userjoined_${groupId}_${randomId}" + ) + + SeqStateDate(_, _, date) ← dialogExt.sendServerMessage( + apiGroupPeer, + senderUserId = cmd.joiningUserId, + senderAuthId = cmd.joiningUserAuthId, + randomId = randomId, + serviceMessage // no delivery tag. This updated handled this way in Groups V1 + ) + } yield SeqStateDate(seq, state, date) + + def joinCHANNELUpdates: Future[SeqStateDate] = + for { + // push all group updates to joiningUserId + _ ← FutureExt.ftraverse(joiningUserUpdatesNew) { update ⇒ + seqUpdExt.deliverUserUpdate(userId = cmd.joiningUserId, update) + } + + // push updated members count to joining user + SeqState(seq, state) ← seqUpdExt.deliverClientUpdate( + userId = cmd.joiningUserId, + authId = cmd.joiningUserAuthId, + update = membersUpdateNew, + deliveryId = s"join_${groupId}_${randomId}" + ) + + // push updated members count to all group members except joining user + _ ← seqUpdExt.broadcastPeopleUpdate( + memberIds - cmd.joiningUserId, + membersUpdateNew, + deliveryId = s"userjoined_${groupId}_${randomId}" + ) + } yield SeqStateDate(seq, state, dateMillis) + + val result: Future[(SeqStateDate, Vector[Int], Long)] = + for { + /////////////////////////// + // Groups V1 API updates // + /////////////////////////// + + // push update about members to all users, except joining user + _ ← seqUpdExt.broadcastPeopleUpdate( + memberIds - cmd.joiningUserId, + membersUpdateObsolete, + pushRules = seqUpdExt.pushRules(isFat = true, None), + deliveryId = s"userjoined_obsolete_${groupId}_${randomId}" + ) + + /////////////////////////// + // Groups V2 API updates // + /////////////////////////// + + seqStateDate ← if (newState.groupType.isChannel) joinCHANNELUpdates else joinGROUPUpdates + + } yield (seqStateDate, memberIds.toVector :+ inviterUserId, randomId) + + result pipeTo sender() + } + } + } + + /** + * This case handled in other manner, so we change state in the end + * cause user that left, should send service message. And we don't allow non-members + * to send message. So we keep him as member until message sent, and remove him from members + */ + protected def leave(cmd: Leave): Unit = { + if (state.nonMember(cmd.userId)) { + sender() ! notMember + } else { + persist(UserLeft(Instant.now, cmd.userId)) { evt ⇒ + // no commit here. it will be after service message sent + + val dateMillis = evt.ts.toEpochMilli + + val updateObsolete = UpdateGroupUserLeaveObsolete(groupId, cmd.userId, dateMillis, cmd.randomId) + + // TODO: merge, they are almost identical + val leftUserUpdatesNew = + if (state.groupType.isChannel) List( + UpdateGroupCanViewMembersChanged(groupId, canViewMembers = false), + UpdateGroupCanEditInfoChanged(groupId, canEditGroup = false), + UpdateGroupCanEditUsernameChanged(groupId, canEditUsername = false), + UpdateGroupCanEditAdminsChanged(groupId, canAssignAdmins = false), + UpdateGroupCanViewAdminsChanged(groupId, canViewAdmins = false), + UpdateGroupCanEditAdminSettingsChanged(groupId, canEditAdminSettings = false), + UpdateGroupCanInviteMembersChanged(groupId, canInviteMembers = false) + ) + else List( + UpdateGroupCanEditInfoChanged(groupId, canEditGroup = false), + UpdateGroupCanEditUsernameChanged(groupId, canEditUsername = false), + UpdateGroupCanEditAdminsChanged(groupId, canAssignAdmins = false), + UpdateGroupCanViewAdminsChanged(groupId, canViewAdmins = false), + UpdateGroupCanEditAdminSettingsChanged(groupId, canEditAdminSettings = false), + UpdateGroupCanViewMembersChanged(groupId, canViewMembers = false), + UpdateGroupMembersUpdated(groupId, members = Vector.empty), + UpdateGroupCanInviteMembersChanged(groupId, canInviteMembers = false) + ) + + val membersUpdateNew = + if (state.groupType.isChannel) { // if channel, or group is big enough + UpdateGroupMembersCountChanged( + groupId, + membersCount = state.membersCount - 1 + ) + } else { + UpdateGroupMembersUpdated( + groupId, + members = state.members.filterNot(_._1 == cmd.userId).values.map(_.asStruct).toVector + ) + } + + val serviceMessage = GroupServiceMessages.userLeft + + //TODO: remove deprecated. GroupInviteTokenRepo don't have replacement yet. + db.run( + for { + _ ← GroupUserRepo.delete(groupId, cmd.userId) + _ ← GroupInviteTokenRepo.revoke(groupId, cmd.userId) + } yield () + ) + + val leaveGROUPUpdates: Future[SeqStateDate] = + for { + // push updated members list to all group members + _ ← seqUpdExt.broadcastPeopleUpdate( + state.memberIds - cmd.userId, + membersUpdateNew + ) + + // send service message + SeqStateDate(_, _, date) ← dialogExt.sendServerMessage( + apiGroupPeer, + senderUserId = cmd.userId, + senderAuthId = cmd.authId, + randomId = cmd.randomId, + message = serviceMessage, + deliveryTag = Some(Optimization.GroupV2) + ) + + // push left user that he is no longer a member + SeqState(seq, state) ← seqUpdExt.deliverClientUpdate( + userId = cmd.userId, + authId = cmd.authId, + update = UpdateGroupMemberChanged(groupId, isMember = false) + ) + + // push left user updates + // • with empty group members + // • that he can't view and invite members + _ ← FutureExt.ftraverse(leftUserUpdatesNew) { update ⇒ + seqUpdExt.deliverUserUpdate(userId = cmd.userId, update) + } + } yield SeqStateDate(seq, state, date) + + val leaveCHANNELUpdates: Future[SeqStateDate] = + for { + // push updated members count to all group members + _ ← seqUpdExt.broadcastPeopleUpdate( + state.memberIds - cmd.userId, + membersUpdateNew + ) + + // push left user that he is no longer a member + SeqState(seq, state) ← seqUpdExt.deliverClientUpdate( + userId = cmd.userId, + authId = cmd.authId, + update = UpdateGroupMemberChanged(groupId, isMember = false) + ) + + // push left user updates that he has no group rights + _ ← FutureExt.ftraverse(leftUserUpdatesNew) { update ⇒ + seqUpdExt.deliverUserUpdate(userId = cmd.userId, update) + } + } yield SeqStateDate(seq, state, dateMillis) + + // read this dialog by user that leaves group. don't wait for ack + dialogExt.messageRead(apiGroupPeer, cmd.userId, 0L, dateMillis) + val result: Future[SeqStateDate] = for { + + /////////////////////////// + // Groups V1 API updates // + /////////////////////////// + + _ ← seqUpdExt.broadcastClientUpdate( + userId = cmd.userId, + authId = cmd.authId, + bcastUserIds = state.memberIds + cmd.userId, // push this to other user's devices too. actually cmd.userId is still in state.memberIds + update = updateObsolete, + pushRules = seqUpdExt.pushRules(isFat = false, Some(PushTexts.Left), Seq(cmd.authId)) + ) + + /////////////////////////// + // Groups V2 API updates // + /////////////////////////// + + seqStateDate ← if (state.groupType.isChannel) leaveCHANNELUpdates else leaveGROUPUpdates + + } yield seqStateDate + + result andThen { case _ ⇒ commit(evt) } pipeTo sender() + } + } + } + + protected def kick(cmd: Kick): Unit = { + if (!state.permissions.canKickMember(cmd.kickerUserId)) { + sender() ! noPermission + } else if (state.nonMember(cmd.kickedUserId)) { + sender() ! notMember + } else { + persist(UserKicked(Instant.now, cmd.kickedUserId, cmd.kickerUserId)) { evt ⇒ + val newState = commit(evt) + + val dateMillis = evt.ts.toEpochMilli + + val updateObsolete = UpdateGroupUserKickObsolete(groupId, cmd.kickedUserId, cmd.kickerUserId, dateMillis, cmd.randomId) + + // TODO: merge, they are almost identical + val kickedUserUpdatesNew: List[Update] = + if (state.groupType.isChannel) List( + UpdateGroupCanViewMembersChanged(groupId, canViewMembers = false), + UpdateGroupCanEditInfoChanged(groupId, canEditGroup = false), + UpdateGroupCanEditUsernameChanged(groupId, canEditUsername = false), + UpdateGroupCanEditAdminsChanged(groupId, canAssignAdmins = false), + UpdateGroupCanViewAdminsChanged(groupId, canViewAdmins = false), + UpdateGroupCanEditAdminSettingsChanged(groupId, canEditAdminSettings = false), + UpdateGroupCanInviteMembersChanged(groupId, canInviteMembers = false), + UpdateGroupMemberChanged(groupId, isMember = false) + ) + else List( + UpdateGroupCanViewMembersChanged(groupId, canViewMembers = false), + UpdateGroupCanEditInfoChanged(groupId, canEditGroup = false), + UpdateGroupCanEditUsernameChanged(groupId, canEditUsername = false), + UpdateGroupCanEditAdminsChanged(groupId, canAssignAdmins = false), + UpdateGroupCanViewAdminsChanged(groupId, canViewAdmins = false), + UpdateGroupCanEditAdminSettingsChanged(groupId, canEditAdminSettings = false), + UpdateGroupCanInviteMembersChanged(groupId, canInviteMembers = false), + UpdateGroupMembersUpdated(groupId, members = Vector.empty), + UpdateGroupMemberChanged(groupId, isMember = false) + ) + + val membersUpdateNew: Update = + if (newState.groupType.isChannel) { // if channel, or group is big enough + UpdateGroupMembersCountChanged( + groupId, + membersCount = newState.membersCount + ) + } else { + UpdateGroupMembersUpdated( + groupId, + members = newState.members.values.map(_.asStruct).toVector + ) + } + + val serviceMessage = GroupServiceMessages.userKicked(cmd.kickedUserId) + + //TODO: remove deprecated. GroupInviteTokenRepo don't have replacement yet. + db.run( + for { + _ ← GroupUserRepo.delete(groupId, cmd.kickedUserId) + _ ← GroupInviteTokenRepo.revoke(groupId, cmd.kickedUserId) + } yield () + ) + + val kickGROUPUpdates: Future[SeqStateDate] = + for { + // push updated members list to all group members. Don't push to kicked user! + SeqState(seq, state) ← seqUpdExt.broadcastClientUpdate( + userId = cmd.kickerUserId, + authId = cmd.kickerAuthId, + bcastUserIds = newState.memberIds - cmd.kickerUserId, + update = membersUpdateNew + ) + + SeqStateDate(_, _, date) ← dialogExt.sendServerMessage( + apiGroupPeer, + senderUserId = cmd.kickerUserId, + senderAuthId = cmd.kickerAuthId, + randomId = cmd.randomId, + message = serviceMessage, + deliveryTag = Some(Optimization.GroupV2) + ) + + // push kicked user updates + // • with empty group members + // • that he is no longer a member of group + // • that he can't view and invite members + _ ← FutureExt.ftraverse(kickedUserUpdatesNew) { update ⇒ + seqUpdExt.deliverUserUpdate(userId = cmd.kickedUserId, update) + } + } yield SeqStateDate(seq, state, date) + + val kickCHANNELUpdates: Future[SeqStateDate] = + for { + // push updated members count to all group members. Don't push to kicked user! + SeqState(seq, state) ← seqUpdExt.broadcastClientUpdate( + userId = cmd.kickerUserId, + authId = cmd.kickerAuthId, + bcastUserIds = newState.memberIds - cmd.kickerUserId, + update = membersUpdateNew + ) + + // push service message to kicker and kicked users. + _ ← seqUpdExt.broadcastPeopleUpdate( + userIds = Set(cmd.kickedUserId, cmd.kickerUserId), + update = serviceMessageUpdate( + cmd.kickerUserId, + dateMillis, + cmd.randomId, + serviceMessage + ), + deliveryTag = Some(Optimization.GroupV2) + ) + + // push kicked user updates that he has no group rights + _ ← FutureExt.ftraverse(kickedUserUpdatesNew) { update ⇒ + seqUpdExt.deliverUserUpdate(userId = cmd.kickedUserId, update) + } + } yield SeqStateDate(seq, state, dateMillis) + + // read this dialog by kicked user. don't wait for ack + dialogExt.messageRead(apiGroupPeer, cmd.kickedUserId, 0L, dateMillis) + val result: Future[SeqStateDate] = for { + /////////////////////////// + // Groups V1 API updates // + /////////////////////////// + + _ ← seqUpdExt.broadcastClientUpdate( + userId = cmd.kickerUserId, + authId = cmd.kickerAuthId, + bcastUserIds = newState.memberIds, + update = updateObsolete, + pushRules = seqUpdExt.pushRules(isFat = false, Some(PushTexts.Kicked), Seq(cmd.kickerAuthId)) + ) + + /////////////////////////// + // Groups V2 API updates // + /////////////////////////// + + seqStateDate ← if (state.groupType.isChannel) kickCHANNELUpdates else kickGROUPUpdates + + } yield seqStateDate + + result pipeTo sender() + } + } + } + + // Updates that will be sent to user, when he enters group. + // Helps clients that have this group to refresh it's data. + // TODO: review when channels will be added + private def refreshGroupUpdates(newState: GroupState, userId: Int): List[Update] = List( + UpdateGroupMemberChanged(groupId, isMember = true), + UpdateGroupAboutChanged(groupId, newState.about), + UpdateGroupAvatarChanged(groupId, newState.avatar), + UpdateGroupTopicChanged(groupId, newState.topic), + UpdateGroupTitleChanged(groupId, newState.title), + UpdateGroupOwnerChanged(groupId, newState.ownerUserId), + UpdateGroupCanViewMembersChanged(groupId, canViewMembers = newState.permissions.canViewMembers(userId)), + UpdateGroupCanInviteMembersChanged(groupId, canInviteMembers = true) // TODO: figure out right value + // UpdateGroupExtChanged(groupId, newState.extension) //TODO: figure out and fix + // if(bigGroup) UpdateGroupMembersCountChanged(groupId, newState.extension) + ) + + private def serviceMessageUpdate(senderUserId: Int, date: Long, randomId: Long, message: ApiServiceMessage) = + UpdateMessage( + peer = apiGroupPeer, + senderUserId = senderUserId, + date = date, + randomId = randomId, + message = message, + attributes = None, + quotedMessage = None + ) + +} diff --git a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupsServiceImpl.scala b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupsServiceImpl.scala index 1e86f599a5..89f4ce821c 100644 --- a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupsServiceImpl.scala +++ b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupsServiceImpl.scala @@ -527,8 +527,10 @@ final class GroupsServiceImpl(groupInviteConfig: GroupInviteConfig)(implicit act authorized(clientData) { implicit client ⇒ withGroupOutPeer(groupPeer) { for { - isPublic ← groupExt.isPublic(groupPeer.groupId) - result ← if (isPublic) { + // TODO: what should it be? was + // isPublic ← groupExt.isPublic(groupPeer.groupId) + isHistoryShared ← groupExt.isHistoryShared(groupPeer.groupId) + result ← if (isHistoryShared) { db.run( for { member ← GroupUserRepo.find(groupPeer.groupId, client.userId) diff --git a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/search/SearchServiceImpl.scala b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/search/SearchServiceImpl.scala index c64c39571b..82c3f34db7 100644 --- a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/search/SearchServiceImpl.scala +++ b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/search/SearchServiceImpl.scala @@ -149,18 +149,15 @@ class SearchServiceImpl(implicit system: ActorSystem) extends SearchService { private def searchGroups(text: Option[String])(implicit client: AuthorizedClientData): Future[IndexedSeq[ApiGroup]] = { for { ids ← DialogExtension(system).fetchGroupedDialogs(client.userId) map (_.filter(_.typ.isGroups).flatMap(_.dialogs.map(_.getPeer.id))) - groupOpts ← FutureExt.ftraverse(ids) { id ⇒ - groupExt.isPublic(id) flatMap { isPublic ⇒ - if (isPublic) FastFuture.successful(None) - else groupExt.getApiStruct(id, client.userId).map(Some(_)) - } + groups ← FutureExt.ftraverse(ids) { id ⇒ + groupExt.getApiStruct(id, client.userId) } - } yield filterGroups(groupOpts.flatten.toVector, text) + } yield filterGroups(groups.toVector, text) } private def searchPublic(text: Option[String])(implicit client: AuthorizedClientData): Future[IndexedSeq[ApiGroup]] = { for { - groups ← db.run(GroupRepo.findPublic) + groups ← db.run(GroupRepo.findPublic) // FIXME: isPublic flag is deprecated and will not appear for new groups groups ← FutureExt.ftraverse(groups)(g ⇒ groupExt.getApiStruct(g.id, client.userId)) } yield filterGroups(groups.toVector, text) } diff --git a/actor-server/src/main/scala/im/actor/server/Main.scala b/actor-server/src/main/scala/im/actor/server/Main.scala index 261b5dab2d..11185c5fbc 100644 --- a/actor-server/src/main/scala/im/actor/server/Main.scala +++ b/actor-server/src/main/scala/im/actor/server/Main.scala @@ -5,4 +5,4 @@ import scala.concurrent.duration.Duration object Main extends App { Await.result(ActorServer.newBuilder.start().system.whenTerminated, Duration.Inf) -} \ No newline at end of file +} From e02470a085b192a36d383392006e1a313fd93ac0 Mon Sep 17 00:00:00 2001 From: rockjam Date: Tue, 19 Jul 2016 18:10:37 +0300 Subject: [PATCH 154/414] fix(server:groups): return current user as member of channel in group queries(Group V1 API) --- .../main/scala/im/actor/server/group/GroupQueryHandlers.scala | 2 +- .../scala/im/actor/server/group/MemberCommandHandlers.scala | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala index 534134b0e7..e1ed6e7cec 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala @@ -199,7 +199,7 @@ trait GroupQueryHandlers { if (state.isAdmin(clientUserId)) apiMembers → group.membersCount else - Vector.empty[ApiMember] → group.membersCount + apiMembers.find(_.userId == clientUserId).toVector → group.membersCount } } else { Vector.empty[ApiMember] → 0 diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/MemberCommandHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/MemberCommandHandlers.scala index 3eeecb9cd0..e974152fbb 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/MemberCommandHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/MemberCommandHandlers.scala @@ -228,6 +228,7 @@ private[group] trait MemberCommandHandlers extends GroupsImplicits { else UpdateGroupMembersUpdated(groupId, apiMembers) // will update date when member got into group + // TODO: not sure how it should be in old API val membersUpdateObsolete = UpdateGroupMembersUpdateObsolete(groupId, apiMembers) val serviceMessage = GroupServiceMessages.userJoined From b9fb7b0a46e0dea8ea8ad22b0903e748927aea6e Mon Sep 17 00:00:00 2001 From: rockjam Date: Tue, 19 Jul 2016 18:12:27 +0300 Subject: [PATCH 155/414] test(server): mark group info spec as pending --- .../im/actor/server/api/rpc/service/GroupsServiceSpec.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actor-server/actor-tests/src/test/scala/im/actor/server/api/rpc/service/GroupsServiceSpec.scala b/actor-server/actor-tests/src/test/scala/im/actor/server/api/rpc/service/GroupsServiceSpec.scala index 2a78c860e4..f9548596d9 100644 --- a/actor-server/actor-tests/src/test/scala/im/actor/server/api/rpc/service/GroupsServiceSpec.scala +++ b/actor-server/actor-tests/src/test/scala/im/actor/server/api/rpc/service/GroupsServiceSpec.scala @@ -68,7 +68,7 @@ final class GroupsServiceSpec "EditGroupAbout" should "allow group admin to change 'about'" in e18 - it should "forbid to change 'about' by non-admin" in e19 + it should "forbid to change 'about' by non-admin" in pendingUntilFixed(e19) it should "set 'about' to empty when None comes" in e20 From 339bd78563032919084cf1c1c2ba4774558ef954 Mon Sep 17 00:00:00 2001 From: rockjam Date: Tue, 19 Jul 2016 21:05:15 +0300 Subject: [PATCH 156/414] chore(server): update actor.json --- .../actor-core/src/main/actor-api/actor.json | 183 ++++++++++++++++++ 1 file changed, 183 insertions(+) diff --git a/actor-server/actor-core/src/main/actor-api/actor.json b/actor-server/actor-core/src/main/actor-api/actor.json index 6246ebdfc2..7ab1ecbfe9 100644 --- a/actor-server/actor-core/src/main/actor-api/actor.json +++ b/actor-server/actor-core/src/main/actor-api/actor.json @@ -8219,6 +8219,24 @@ "argument": "canEditAdminSettings", "category": "full", "description": " If not set only owner can edit admin settings" + }, + { + "type": "reference", + "argument": "canInviteViaLink", + "category": "full", + "description": " If user can invite via link, default is false" + }, + { + "type": "reference", + "argument": "canDelete", + "category": "full", + "description": " If user can delete this group, default is false" + }, + { + "type": "reference", + "argument": "canLeave", + "category": "hidden", + "description": " If user can leave this group, default is true" } ], "expandable": "true", @@ -8367,6 +8385,30 @@ }, "id": 18, "name": "canEditAdminSettings" + }, + { + "type": { + "type": "opt", + "childType": "bool" + }, + "id": 19, + "name": "canInviteViaLink" + }, + { + "type": { + "type": "opt", + "childType": "bool" + }, + "id": 20, + "name": "canDelete" + }, + { + "type": { + "type": "opt", + "childType": "bool" + }, + "id": 21, + "name": "canLeave" } ] } @@ -9130,6 +9172,117 @@ ] } }, + { + "type": "update", + "content": { + "name": "GroupCanInviteViaLink", + "header": 2646, + "doc": [ + "Update about can invite via link changed", + { + "type": "reference", + "argument": "groupId", + "category": "full", + "description": " Group Id" + }, + { + "type": "reference", + "argument": "canInviteViaLink", + "category": "full", + "description": " Can Invite Via link" + } + ], + "attributes": [ + { + "type": { + "type": "alias", + "childType": "groupId" + }, + "id": 1, + "name": "groupId" + }, + { + "type": "bool", + "id": 2, + "name": "canInviteViaLink" + } + ] + } + }, + { + "type": "update", + "content": { + "name": "GroupCanLeaveChanged", + "header": 2647, + "doc": [ + "Update about can leave changed", + { + "type": "reference", + "argument": "groupId", + "category": "full", + "description": " Group Id" + }, + { + "type": "reference", + "argument": "canLeaveChanged", + "category": "full", + "description": " Can leave changed" + } + ], + "attributes": [ + { + "type": { + "type": "alias", + "childType": "groupId" + }, + "id": 1, + "name": "groupId" + }, + { + "type": "bool", + "id": 2, + "name": "canLeaveChanged" + } + ] + } + }, + { + "type": "update", + "content": { + "name": "GroupCanDeleteChanged", + "header": 2648, + "doc": [ + "Update about can delete changed", + { + "type": "reference", + "argument": "groupId", + "category": "full", + "description": " Group Id" + }, + { + "type": "reference", + "argument": "canDeleteChanged", + "category": "full", + "description": " Can delete changed" + } + ], + "attributes": [ + { + "type": { + "type": "alias", + "childType": "groupId" + }, + "id": 1, + "name": "groupId" + }, + { + "type": "bool", + "id": 2, + "name": "canDeleteChanged" + } + ] + } + }, { "type": "update", "content": { @@ -10574,6 +10727,36 @@ ] } }, + { + "type": "rpc", + "content": { + "name": "DeleteGroup", + "header": 2795, + "response": { + "type": "reference", + "name": "Seq" + }, + "doc": [ + "Delete Group", + { + "type": "reference", + "argument": "groupPeer", + "category": "full", + "description": "Group's peer" + } + ], + "attributes": [ + { + "type": { + "type": "struct", + "childType": "GroupOutPeer" + }, + "id": 1, + "name": "groupPeer" + } + ] + } + }, { "type": "comment", "content": "Invite" From a8bd07d5d2a998875ab118e4ec72b69be6dd86f5 Mon Sep 17 00:00:00 2001 From: rockjam Date: Tue, 19 Jul 2016 21:07:26 +0300 Subject: [PATCH 157/414] fix(server:groups): show invite info for groups with short name --- .../server/group/AdminCommandHandlers.scala | 1 + .../server/group/GroupQueryHandlers.scala | 5 +- .../im/actor/server/group/GroupState.scala | 2 +- .../server/group/http/GroupsHttpHandler.scala | 58 +++++++++++-------- .../server/names/GlobalNamesStorage.scala | 4 +- .../file/local/FileStorageOperations.scala | 8 +-- .../server/api/http/json/JsonFormatters.scala | 6 +- .../actor/server/api/http/json/models.scala | 8 +-- .../scala/im/actor/server/persist/Group.scala | 1 - .../api/rpc/service/auth/AuthHelpers.scala | 4 +- .../rpc/service/auth/AuthServiceImpl.scala | 2 +- .../contacts/ContactsServiceImpl.scala | 2 +- .../service/groups/GroupsServiceImpl.scala | 4 +- .../server/api/http/HttpApiFrontendSpec.scala | 18 ++++++ 14 files changed, 77 insertions(+), 46 deletions(-) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/AdminCommandHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/AdminCommandHandlers.scala index 33d1833f89..6d304eaa74 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/AdminCommandHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/AdminCommandHandlers.scala @@ -302,6 +302,7 @@ private[group] trait AdminCommandHandlers extends GroupsImplicits { } else { persist(HistoryBecameShared(Instant.now, cmd.clientUserId)) { evt ⇒ val newState = commit(evt) + log.debug("History of group {} became shared", groupId) val memberIds = newState.memberIds diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala index e1ed6e7cec..e6de46c236 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala @@ -135,7 +135,10 @@ trait GroupQueryHandlers { canEditShortName = Some(state.permissions.canEditShortName(clientUserId)), canEditAdminList = Some(state.permissions.canEditAdmins(clientUserId)), canViewAdminList = Some(state.permissions.canViewAdmins(clientUserId)), - canEditAdminSettings = Some(state.permissions.canEditAdminSettings(clientUserId)) + canEditAdminSettings = Some(state.permissions.canEditAdminSettings(clientUserId)), + canInviteViaLink = None, + canDelete = None, + canLeave = None ) ) } diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala index 80b6fb0da0..77ec5178ab 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala @@ -108,7 +108,7 @@ private[group] final case class GroupState( avatar: Option[Avatar], topic: Option[String], shortName: Option[String], - groupType: GroupType, // TODO: rename to groupType + groupType: GroupType, isHidden: Boolean, isHistoryShared: Boolean, diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/http/GroupsHttpHandler.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/http/GroupsHttpHandler.scala index 0e64b70c2a..e8d3b80506 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/http/GroupsHttpHandler.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/http/GroupsHttpHandler.scala @@ -6,36 +6,33 @@ import akka.http.scaladsl.model.StatusCodes._ import akka.http.scaladsl.server.Directives._ import akka.http.scaladsl.server.Route import akka.http.scaladsl.util.FastFuture -import im.actor.server.api.http.{ HttpHandler, json } import im.actor.server.api.http.json.JsonFormatters.{ errorsFormat, groupInviteInfoFormat } +import im.actor.server.api.http.{ HttpHandler, json } import im.actor.server.db.DbExtension -import im.actor.server.file.ImageUtils.getAvatar import im.actor.server.file.{ Avatar, FileLocation, FileStorageExtension } import im.actor.server.group.GroupExtension -import im.actor.server.model.AvatarData +import im.actor.server.names.GlobalNamesStorageKeyValueStorage import im.actor.server.persist._ import im.actor.server.persist.files.FileRepo import im.actor.server.user.UserExtension import play.api.libs.json.Json -import slick.driver.PostgresDriver.api._ import scala.concurrent.Future -import scala.concurrent.duration._ import scala.util.{ Failure, Success } private[group] final class GroupsHttpHandler()(implicit system: ActorSystem) extends HttpHandler { import im.actor.server.ApiConversions._ - import system.dispatcher private val db = DbExtension(system).db private val fsAdapter = FileStorageExtension(system).fsAdapter + private val globalNamesStorage = new GlobalNamesStorageKeyValueStorage override def routes: Route = defaultVersion { - path("groups" / "invites" / Segment) { token ⇒ + path("groups" / "invites" / Segment) { tokenOrShortName ⇒ get { - onComplete(retrieve(token)) { + onComplete(retrieve(tokenOrShortName)) { case Success(Right(result)) ⇒ complete(HttpResponse( status = OK, @@ -52,25 +49,36 @@ private[group] final class GroupsHttpHandler()(implicit system: ActorSystem) ext } } - private def retrieve(token: String): Future[Either[json.Errors, json.GroupInviteInfo]] = for { - optTokenInfo ← db.run(GroupInviteTokenRepo.findByToken(token)) - result ← optTokenInfo map { tokenInfo ⇒ - for { - groupInfo ← GroupExtension(system).getApiStruct(tokenInfo.groupId, 0) - groupTitle = groupInfo.title - groupAvatar = groupInfo.avatar - groupAvatarUrls ← avatarUrls(groupAvatar) + private def retrieve(tokenOrShortName: String): Future[Either[json.Errors, json.GroupInviteInfo]] = for { + byToken ← db.run(GroupInviteTokenRepo.findByToken(tokenOrShortName)) + byGroupId ← globalNamesStorage.getGroupId(tokenOrShortName) + optInviteData = (byToken, byGroupId) match { + case (Some(tokenInfo), _) ⇒ Some(tokenInfo.groupId → Some(tokenInfo.creatorId)) + case (_, Some(groupId)) ⇒ Some(groupId → None) + case _ ⇒ None + } + result ← optInviteData map { + case (groupId, optInviterId) ⇒ + for { + groupInfo ← GroupExtension(system).getApiStruct(groupId, 0) + groupTitle = groupInfo.title + groupAvatar = groupInfo.avatar + groupAvatarUrls ← avatarUrls(groupAvatar) - inviterInfo ← UserExtension(system).getApiStruct(tokenInfo.creatorId, 0, 0L) - inviterName = inviterInfo.name - inviterAvatar = inviterInfo.avatar - inviterAvatarUrls ← avatarUrls(inviterAvatar) - } yield Right( - json.GroupInviteInfo( - group = json.Group(groupTitle, groupAvatarUrls), - inviter = json.User(inviterName, inviterAvatarUrls) + optInviterInfo ← optInviterId match { + case Some(inviterId) ⇒ + for { + user ← UserExtension(system).getApiStruct(inviterId, 0, 0L) + avatars ← avatarUrls(user.avatar) + } yield Some(json.InviterInfo(user.name, avatars)) + case None ⇒ FastFuture.successful(None) + } + } yield Right( + json.GroupInviteInfo( + group = json.GroupInfo(groupTitle, groupAvatarUrls), + inviter = optInviterInfo + ) ) - ) } getOrElse FastFuture.successful(Left(json.Errors("Expired or invalid token"))) } yield result diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/names/GlobalNamesStorage.scala b/actor-server/actor-core/src/main/scala/im/actor/server/names/GlobalNamesStorage.scala index a24cc6b529..d6cae1bc9e 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/names/GlobalNamesStorage.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/names/GlobalNamesStorage.scala @@ -29,12 +29,12 @@ final class GlobalNamesStorageKeyValueStorage(implicit system: ActorSystem) { (ext.db, ext.connector) } - def getUserOwnerId(name: String): Future[Option[Int]] = + def getUserId(name: String): Future[Option[Int]] = getOwner(name) map (_.collect { case GlobalNameOwner(OwnerType.User, userId) ⇒ userId }) - def getGroupOwnerId(name: String): Future[Option[Int]] = + def getGroupId(name: String): Future[Option[Int]] = getOwner(name) map (_.collect { case GlobalNameOwner(OwnerType.Group, groupId) ⇒ groupId }) diff --git a/actor-server/actor-fs-adapters/src/main/scala/im/actor/server/file/local/FileStorageOperations.scala b/actor-server/actor-fs-adapters/src/main/scala/im/actor/server/file/local/FileStorageOperations.scala index 7d1d341d82..c1d593176a 100644 --- a/actor-server/actor-fs-adapters/src/main/scala/im/actor/server/file/local/FileStorageOperations.scala +++ b/actor-server/actor-fs-adapters/src/main/scala/im/actor/server/file/local/FileStorageOperations.scala @@ -6,15 +6,15 @@ import akka.actor.ActorSystem import akka.event.Logging import akka.http.scaladsl.util.FastFuture import akka.stream.Materializer -import akka.stream.scaladsl.{FileIO, Source} +import akka.stream.scaladsl.{ FileIO, Source } import akka.util.ByteString -import better.files.{File, _} +import better.files.{ File, _ } import im.actor.server.db.DbExtension import im.actor.server.file.UnsafeFileName import im.actor.server.persist.files.FileRepo -import scala.concurrent.{ExecutionContext, Future, blocking} -import scala.util.{Failure, Success} +import scala.concurrent.{ ExecutionContext, Future, blocking } +import scala.util.{ Failure, Success } trait FileStorageOperations extends LocalUploadKeyImplicits { diff --git a/actor-server/actor-http-api/src/main/scala/im/actor/server/api/http/json/JsonFormatters.scala b/actor-server/actor-http-api/src/main/scala/im/actor/server/api/http/json/JsonFormatters.scala index 348aaa1f84..1c203836de 100644 --- a/actor-server/actor-http-api/src/main/scala/im/actor/server/api/http/json/JsonFormatters.scala +++ b/actor-server/actor-http-api/src/main/scala/im/actor/server/api/http/json/JsonFormatters.scala @@ -11,8 +11,8 @@ object JsonFormatters { (JsPath \ "image_url").read[String].map[Content] { Image } implicit val avatarUrlsFormat: Format[AvatarUrls] = Json.format[AvatarUrls] - implicit val userFormat: Format[User] = Json.format[User] - implicit val groupFormat: Format[Group] = Json.format[Group] + implicit val userFormat: Format[InviterInfo] = Json.format[InviterInfo] + implicit val groupFormat: Format[GroupInfo] = Json.format[GroupInfo] implicit val groupInviteInfoFormat: Format[GroupInviteInfo] = Json.format[GroupInviteInfo] implicit val errorsFormat: Format[Errors] = Json.format[Errors] @@ -22,4 +22,4 @@ object JsonFormatters { implicit val reverseHookResponseFormat: Format[ReverseHookResponse] = Json.format[ReverseHookResponse] implicit val statusFormat: Format[Status] = Json.format[Status] -} \ No newline at end of file +} diff --git a/actor-server/actor-http-api/src/main/scala/im/actor/server/api/http/json/models.scala b/actor-server/actor-http-api/src/main/scala/im/actor/server/api/http/json/models.scala index 2758eba68a..fc84bec8a4 100644 --- a/actor-server/actor-http-api/src/main/scala/im/actor/server/api/http/json/models.scala +++ b/actor-server/actor-http-api/src/main/scala/im/actor/server/api/http/json/models.scala @@ -5,9 +5,9 @@ case class Text(text: String) extends Content case class Image(imageUrl: String) extends Content case class Document(documentUrl: String) extends Content -case class Group(title: String, avatars: Option[AvatarUrls]) -case class User(name: String, avatars: Option[AvatarUrls]) -case class GroupInviteInfo(group: Group, inviter: User) +case class GroupInfo(title: String, avatars: Option[AvatarUrls]) +case class InviterInfo(name: String, avatars: Option[AvatarUrls]) +case class GroupInviteInfo(group: GroupInfo, inviter: Option[InviterInfo]) case class AvatarUrls(small: Option[String], large: Option[String], full: Option[String]) case class Errors(message: String) @@ -17,4 +17,4 @@ case class ReverseHook(url: String) case class Status(status: String) case class ReverseHookResponse(id: Int, url: Option[String]) -final case class ServerInfo(projectName: String, endpoints: List[String]) \ No newline at end of file +final case class ServerInfo(projectName: String, endpoints: List[String]) diff --git a/actor-server/actor-persist/src/main/scala/im/actor/server/persist/Group.scala b/actor-server/actor-persist/src/main/scala/im/actor/server/persist/Group.scala index 6d46810bd0..e5aeda7470 100644 --- a/actor-server/actor-persist/src/main/scala/im/actor/server/persist/Group.scala +++ b/actor-server/actor-persist/src/main/scala/im/actor/server/persist/Group.scala @@ -93,7 +93,6 @@ object GroupRepo { ) } - @deprecated("Public groups are deprecated in Group V2 API", "2016-06-05") def findPublic = groups.filter(_.isPublic === true).map(_.asGroup).result diff --git a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/auth/AuthHelpers.scala b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/auth/AuthHelpers.scala index 4b2dd15804..4a8471313c 100644 --- a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/auth/AuthHelpers.scala +++ b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/auth/AuthHelpers.scala @@ -67,7 +67,7 @@ trait AuthHelpers extends Helpers { protected def newUsernameSignUp(transaction: AuthUsernameTransaction, name: String, sex: Option[ApiSex]): Result[(Int, String) Xor User] = { val username = transaction.username for { - optUserId ← fromFuture(globalNamesStorage.getUserOwnerId(username)) + optUserId ← fromFuture(globalNamesStorage.getUserId(username)) result ← optUserId match { case Some(id) ⇒ point(Xor.left((id, ""))) case None ⇒ newUser(name, "", sex, username = Some(username)) @@ -168,7 +168,7 @@ trait AuthHelpers extends Helpers { } yield (emailModel.userId, "") case u: AuthUsernameTransaction ⇒ for { - userId ← fromFutureOption(AuthErrors.UsernameUnoccupied)(globalNamesStorage.getUserOwnerId(u.username)) + userId ← fromFutureOption(AuthErrors.UsernameUnoccupied)(globalNamesStorage.getUserId(u.username)) } yield (userId, "") case _: AuthAnonymousTransaction ⇒ fromEither(Xor.left(AuthErrors.NotValidated)) diff --git a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/auth/AuthServiceImpl.scala b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/auth/AuthServiceImpl.scala index 4f49ce914c..bfa32c52a8 100644 --- a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/auth/AuthServiceImpl.scala +++ b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/auth/AuthServiceImpl.scala @@ -202,7 +202,7 @@ final class AuthServiceImpl(val oauth2Service: GoogleProvider)( val action = for { normUsername ← fromOption(ProfileRpcErrors.NicknameInvalid)(StringUtils.normalizeUsername(username)) - optUserId ← fromFuture(globalNamesStorage.getUserOwnerId(username)) + optUserId ← fromFuture(globalNamesStorage.getUserId(username)) _ ← optUserId map (id ⇒ forbidDeletedUser(id)) getOrElse point(()) optAuthTransaction ← fromDBIO(AuthUsernameTransactionRepo.find(username, deviceHash)) transactionHash ← optAuthTransaction match { diff --git a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/contacts/ContactsServiceImpl.scala b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/contacts/ContactsServiceImpl.scala index 362e3fc3ac..5ac43701dc 100644 --- a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/contacts/ContactsServiceImpl.scala +++ b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/contacts/ContactsServiceImpl.scala @@ -172,7 +172,7 @@ class ContactsServiceImpl(implicit actorSystem: ActorSystem) private def findByNickname(nickname: String, client: AuthorizedClientData): Result[Vector[ApiUser]] = { for { - optUserId ← fromFuture(globalNamesStorage.getUserOwnerId(nickname)) + optUserId ← fromFuture(globalNamesStorage.getUserId(nickname)) structs ← fromFuture(Future.sequence(optUserId.toSeq map (userId ⇒ userExt.getApiStruct(userId, client.userId, client.authId)))) } yield structs.toVector } diff --git a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupsServiceImpl.scala b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupsServiceImpl.scala index 89f4ce821c..fc654f9019 100644 --- a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupsServiceImpl.scala +++ b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupsServiceImpl.scala @@ -369,7 +369,7 @@ final class GroupsServiceImpl(groupInviteConfig: GroupInviteConfig)(implicit act } yield info.groupId → Some(info.creatorId) case Xor.Right(groupName) ⇒ for { - groupId ← fromFutureOption(GroupRpcErrors.InvalidInviteGroup)(globalNamesStorage.getGroupOwnerId(groupName)) + groupId ← fromFutureOption(GroupRpcErrors.InvalidInviteGroup)(globalNamesStorage.getGroupId(groupName)) } yield groupId → None } (groupId, optInviter) = joinInfo @@ -459,6 +459,8 @@ final class GroupsServiceImpl(groupInviteConfig: GroupInviteConfig)(implicit act } } + protected def doHandleDeleteGroup(groupPeer: ApiGroupOutPeer, clientData: ClientData): Future[HandlerResult[ResponseSeq]] = ??? + private def usersOrPeers(userIds: Vector[Int], stripEntities: Boolean)(implicit client: AuthorizedClientData): Future[(Vector[ApiUser], Vector[ApiUserOutPeer])] = if (stripEntities) { val users = Vector.empty[ApiUser] diff --git a/actor-server/actor-tests/src/test/scala/im/actor/server/api/http/HttpApiFrontendSpec.scala b/actor-server/actor-tests/src/test/scala/im/actor/server/api/http/HttpApiFrontendSpec.scala index 031d453358..bb5ad0d311 100644 --- a/actor-server/actor-tests/src/test/scala/im/actor/server/api/http/HttpApiFrontendSpec.scala +++ b/actor-server/actor-tests/src/test/scala/im/actor/server/api/http/HttpApiFrontendSpec.scala @@ -56,6 +56,8 @@ final class HttpApiFrontendSpec "Groups handler" should "respond with JSON message to group invite info with correct invite token" in t.groupInvitesOk() + it should "respond with JSON message without inviter, when we join via group short name" in t.groupInvitesShortName() + it should "respond with JSON message with avatar full links to group invite info with correct invite token" in t.groupInvitesAvatars1() it should "respond with JSON message with avatar partial links to group invite info with correct invite token" in t.groupInvitesAvatars2() @@ -316,6 +318,22 @@ final class HttpApiFrontendSpec } } + def groupInvitesShortName() = { + val shortName = "division" + whenReady(groupExt.updateShortName(groupOutPeer.groupId, user1.id, authId1, Some(shortName))) { _ ⇒ + val request = HttpRequest( + method = GET, + uri = s"${config.baseUri}/v1/groups/invites/$shortName" + ) + val resp = singleRequest(request).futureValue + resp.status shouldEqual OK + val body = resp.entity.asString + val response = Json.parse(body) + (response \ "group" \ "title").as[String] shouldEqual groupName + (response \ "inviter" \ "name").toOption shouldEqual None + } + } + def groupInvitesAvatars1() = { val avatarData = Files.readAllBytes(Paths.get(getClass.getResource("/valid-avatar.jpg").toURI)) val fileLocation = db.run(fsAdapter.uploadFile(UnsafeFileName("avatar"), avatarData)).futureValue From 563be548f4e8093692d11cf1408ecff2def17bca Mon Sep 17 00:00:00 2001 From: rockjam Date: Tue, 19 Jul 2016 22:23:30 +0300 Subject: [PATCH 158/414] fix(server:groups): add missing serializer ids --- .../src/main/scala/im/actor/server/group/GroupProcessor.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala index fc7b92117d..b6c9c45594 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala @@ -49,6 +49,7 @@ object GroupProcessor { 20022 → classOf[GroupCommands.UpdateShortName], 20023 → classOf[GroupCommands.DismissUserAdmin], 20024 → classOf[GroupCommands.UpdateAdminSettings], + 20025 → classOf[GroupCommands.MakeHistoryShared], 21001 → classOf[GroupQueries.GetIntegrationToken], 21002 → classOf[GroupQueries.GetIntegrationTokenResponse], @@ -88,7 +89,8 @@ object GroupProcessor { 22017 → classOf[GroupEvents.OwnerChanged], 22018 → classOf[GroupEvents.ShortNameUpdated], 22019 → classOf[GroupEvents.AdminSettingsUpdated], - 22020 → classOf[GroupEvents.AdminStatusChanged] + 22020 → classOf[GroupEvents.AdminStatusChanged], + 22021 → classOf[GroupEvents.HistoryBecameShared] ) def persistenceIdFor(groupId: Int): String = s"Group-${groupId}" From 73b1698e017ef6ec3a529f31899680674165f21e Mon Sep 17 00:00:00 2001 From: rockjam Date: Wed, 20 Jul 2016 02:09:47 +0300 Subject: [PATCH 159/414] chore(server): update actor.json --- .../actor-core/src/main/actor-api/actor.json | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/actor-server/actor-core/src/main/actor-api/actor.json b/actor-server/actor-core/src/main/actor-api/actor.json index 7ab1ecbfe9..2fe4b769ee 100644 --- a/actor-server/actor-core/src/main/actor-api/actor.json +++ b/actor-server/actor-core/src/main/actor-api/actor.json @@ -10757,6 +10757,36 @@ ] } }, + { + "type": "rpc", + "content": { + "name": "ShareHistory", + "header": 2796, + "response": { + "type": "reference", + "name": "Seq" + }, + "doc": [ + "Share History", + { + "type": "reference", + "argument": "groupPeer", + "category": "full", + "description": "Group's peer" + } + ], + "attributes": [ + { + "type": { + "type": "struct", + "childType": "GroupOutPeer" + }, + "id": 1, + "name": "groupPeer" + } + ] + } + }, { "type": "comment", "content": "Invite" From a2cc35ad853dfe6c20283ee32d91eb7e6a8ae26c Mon Sep 17 00:00:00 2001 From: rockjam Date: Wed, 20 Jul 2016 04:10:21 +0300 Subject: [PATCH 160/414] feat(server:groups): delete group, make group with shared history, correct push updates on join/leave, more FullGroup flags --- .../actor-core/src/main/protobuf/group.proto | 7 ++ .../src/main/protobuf/groupV2.proto | 8 ++ .../src/main/scala/im/actor/api/rpc.scala | 10 ++- .../server/group/AdminCommandHandlers.scala | 70 +++++++++++++++- .../im/actor/server/group/GroupErrors.scala | 4 + .../actor/server/group/GroupOperations.scala | 5 ++ .../actor/server/group/GroupProcessor.scala | 14 ++-- .../server/group/GroupQueryHandlers.scala | 6 +- .../im/actor/server/group/GroupState.scala | 23 ++++++ .../server/group/MemberCommandHandlers.scala | 82 +++++++++++-------- .../actor/server/sequence/Optimization.scala | 5 +- .../scala/im/actor/api/rpc/PeerHelpers.scala | 9 +- .../rpc/service/files/FilesServiceImpl.scala | 2 +- .../rpc/service/groups/GroupRpcErrors.scala | 1 + .../service/groups/GroupsServiceImpl.scala | 21 ++++- .../service/messaging/HistoryHandlers.scala | 1 + .../service/messaging/MessagingHandlers.scala | 4 +- 17 files changed, 214 insertions(+), 58 deletions(-) diff --git a/actor-server/actor-core/src/main/protobuf/group.proto b/actor-server/actor-core/src/main/protobuf/group.proto index bfc8566ae2..654125b819 100644 --- a/actor-server/actor-core/src/main/protobuf/group.proto +++ b/actor-server/actor-core/src/main/protobuf/group.proto @@ -155,4 +155,11 @@ message GroupEvents { required int64 ts = 1 [(scalapb.field).type = "java.time.Instant"]; required int32 executor_user_id = 2; } + + message GroupDeleted { + option (scalapb.message).extends = "im.actor.server.group.GroupEvent"; + + required int64 ts = 1 [(scalapb.field).type = "java.time.Instant"]; + required int32 executor_user_id = 2; + } } diff --git a/actor-server/actor-core/src/main/protobuf/groupV2.proto b/actor-server/actor-core/src/main/protobuf/groupV2.proto index 2f8eaee56c..3d524d5339 100644 --- a/actor-server/actor-core/src/main/protobuf/groupV2.proto +++ b/actor-server/actor-core/src/main/protobuf/groupV2.proto @@ -37,6 +37,7 @@ message GroupEnvelope { GroupCommands.DismissUserAdmin dismiss_user_admin = 28; GroupCommands.TransferOwnership transfer_ownership = 14; GroupCommands.UpdateAdminSettings update_admin_settings = 30; + GroupCommands.DeleteGroup delete_group = 33; } oneof query { GroupQueries.GetAccessHash get_access_hash = 15; @@ -204,6 +205,13 @@ message GroupCommands { int32 client_user_id = 1; int64 client_auth_id = 2; } + + message DeleteGroup { + option (scalapb.message).extends = "GroupCommand"; + + int32 client_user_id = 1; + int64 client_auth_id = 2; + } } message GroupQueries { diff --git a/actor-server/actor-core/src/main/scala/im/actor/api/rpc.scala b/actor-server/actor-core/src/main/scala/im/actor/api/rpc.scala index 66cb42fe6d..3647567cd9 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/api/rpc.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/api/rpc.scala @@ -3,7 +3,7 @@ package im.actor.api import akka.http.scaladsl.util.FastFuture import cats.data.Xor import im.actor.server.CommonErrors -import im.actor.server.group.GroupErrors.GroupNotFound +import im.actor.server.group.GroupErrors.{ GroupAlreadyDeleted, GroupNotFound } import im.actor.server.office.EntityNotFoundError import im.actor.server.user.UserErrors.UserNotFound @@ -16,6 +16,7 @@ package object rpc extends PeersImplicits with HistoryImplicits with DialogConve object CommonRpcErrors { val GroupNotFound = RpcError(404, "GROUP_NOT_FOUND", "", false, None) + val GroupDeleted = RpcError(404, "GROUP_DELETED", "", false, None) val InvalidAccessHash = RpcError(403, "INVALID_ACCESS_HASH", "", false, None) val UnsupportedRequest = RpcError(400, "REQUEST_NOT_SUPPORTED", "Operation not supported.", false, None) val UserNotAuthorized = RpcError(403, "USER_NOT_AUTHORIZED", "", false, None) @@ -23,14 +24,15 @@ package object rpc extends PeersImplicits with HistoryImplicits with DialogConve val UserPhoneNotFound = RpcError(404, "USER_PHONE_NOT_FOUND", "", false, None) val EntityNotFound = RpcError(404, "ENTITY_NOT_FOUND", "", false, None) val NotSupportedInOss = RpcError(400, "NOT_SUPPORTED_IN_OSS", "Feature is not supported in the Open-Source version.", canTryAgain = false, None) - val IntenalError = RpcError(500, "INTERNAL_ERROR", "", false, None) + val InternalError = RpcError(500, "INTERNAL_ERROR", "", false, None) def forbidden(userMessage: String = "You are not allowed to do this.") = RpcError(403, "FORBIDDEN", userMessage, false, None) } def recoverCommon: PartialFunction[Throwable, RpcError] = { - case UserNotFound(_) ⇒ CommonRpcErrors.UserNotFound - case GroupNotFound(_) ⇒ CommonRpcErrors.GroupNotFound + case _: UserNotFound ⇒ CommonRpcErrors.UserNotFound + case _: GroupNotFound ⇒ CommonRpcErrors.GroupNotFound + case _: GroupAlreadyDeleted ⇒ CommonRpcErrors.GroupDeleted case EntityNotFoundError ⇒ CommonRpcErrors.EntityNotFound case CommonErrors.Forbidden(message) ⇒ CommonRpcErrors.forbidden(message) } diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/AdminCommandHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/AdminCommandHandlers.scala index 6d304eaa74..e3dda1402f 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/AdminCommandHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/AdminCommandHandlers.scala @@ -7,13 +7,14 @@ import akka.pattern.pipe import akka.http.scaladsl.util.FastFuture import im.actor.api.rpc.{ PeersImplicits, Update } import im.actor.api.rpc.groups._ +import im.actor.api.rpc.messaging.UpdateChatClear import im.actor.concurrent.FutureExt import im.actor.server.CommonErrors import im.actor.server.acl.ACLUtils -import im.actor.server.group.GroupCommands.{ DismissUserAdmin, MakeHistoryShared, MakeUserAdmin, RevokeIntegrationToken, RevokeIntegrationTokenAck, TransferOwnership, UpdateAdminSettings, UpdateAdminSettingsAck } +import im.actor.server.group.GroupCommands.{ DeleteGroup, DismissUserAdmin, MakeHistoryShared, MakeUserAdmin, RevokeIntegrationToken, RevokeIntegrationTokenAck, TransferOwnership, UpdateAdminSettings, UpdateAdminSettingsAck } import im.actor.server.group.GroupErrors.{ NotAMember, NotAdmin, UserAlreadyAdmin, UserAlreadyNotAdmin } -import im.actor.server.group.GroupEvents.{ AdminSettingsUpdated, AdminStatusChanged, HistoryBecameShared, IntegrationTokenRevoked, OwnerChanged } -import im.actor.server.persist.{ GroupBotRepo, GroupUserRepo } +import im.actor.server.group.GroupEvents.{ AdminSettingsUpdated, AdminStatusChanged, GroupDeleted, HistoryBecameShared, IntegrationTokenRevoked, OwnerChanged } +import im.actor.server.persist.{ GroupBotRepo, GroupUserRepo, HistoryMessageRepo } import im.actor.server.sequence.{ SeqState, SeqStateDate } import scala.concurrent.Future @@ -225,7 +226,25 @@ private[group] trait AdminCommandHandlers extends GroupsImplicits { val newState = commit(evt) val memberIds = newState.memberIds + val prevOwnerUpdates = List( + UpdateGroupCanLeaveChanged(groupId, canLeaveChanged = true), + UpdateGroupCanDeleteChanged(groupId, canDeleteChanged = false) + ) + + val newOwnerUpdates = List( + UpdateGroupCanLeaveChanged(groupId, canLeaveChanged = false), + UpdateGroupCanDeleteChanged(groupId, canDeleteChanged = true) + ) + val result: Future[SeqState] = for { + // push updates to previous owner + _ ← FutureExt.ftraverse(prevOwnerUpdates) { update ⇒ + seqUpdExt.deliverUserUpdate(cmd.clientUserId, update) + } + // push updates to new owner + _ ← FutureExt.ftraverse(newOwnerUpdates) { update ⇒ + seqUpdExt.deliverUserUpdate(cmd.newOwnerId, update) + } seqState ← seqUpdExt.broadcastClientUpdate( userId = cmd.clientUserId, authId = cmd.clientAuthId, @@ -320,4 +339,49 @@ private[group] trait AdminCommandHandlers extends GroupsImplicits { } } + protected def deleteGroup(cmd: DeleteGroup): Unit = { + if (!state.permissions.canDelete(cmd.clientUserId)) { + sender() ! noPermission + } else { + persist(GroupDeleted(Instant.now, cmd.clientUserId)) { evt ⇒ + val newState = commit(evt) + + val deleteGroupMembersUpdates: Vector[Update] = Vector( + UpdateGroupCanSendMessagesChanged(groupId, canSendMessages = false), + UpdateGroupCanViewMembersChanged(groupId, canViewMembers = false), + UpdateGroupCanEditInfoChanged(groupId, canEditGroup = false), + UpdateGroupCanEditUsernameChanged(groupId, canEditUsername = false), + UpdateGroupCanEditAdminsChanged(groupId, canAssignAdmins = false), + UpdateGroupCanViewAdminsChanged(groupId, canViewAdmins = false), + UpdateGroupCanEditAdminSettingsChanged(groupId, canEditAdminSettings = false), + UpdateGroupCanInviteMembersChanged(groupId, canInviteMembers = false), + UpdateGroupCanInviteViaLink(groupId, canInviteViaLink = false), + UpdateGroupCanLeaveChanged(groupId, canLeaveChanged = false), + UpdateGroupCanDeleteChanged(groupId, canDeleteChanged = false), + UpdateGroupMemberChanged(groupId, isMember = false), + // if channel, or group is big enough + if (newState.groupType.isChannel) + UpdateGroupMembersCountChanged(groupId, membersCount = 0) + else + UpdateGroupMembersUpdated(groupId, members = Vector.empty) + ) + + val result: Future[SeqState] = for { + _ ← db.run(HistoryMessageRepo.deleteAll(cmd.clientUserId, apiGroupPeer.asModel)) + _ ← Future.traverse(deleteGroupMembersUpdates) { update ⇒ + seqUpdExt.broadcastPeopleUpdate(newState.memberIds, update) + } + seqState ← seqUpdExt.broadcastClientUpdate( + cmd.clientUserId, + cmd.clientAuthId, + bcastUserIds = state.memberIds - cmd.clientUserId, + update = UpdateChatClear(apiGroupPeer) + ) + } yield seqState + + result pipeTo sender() + } + } + } + } diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupErrors.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupErrors.scala index 06174a95c5..d2e06a8c18 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupErrors.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupErrors.scala @@ -7,6 +7,8 @@ import scala.util.control.NoStackTrace object GroupErrors { final case class GroupNotFound(id: Int) extends EntityNotFound(s"Group $id not found") + final case class GroupAlreadyDeleted(id: Int) extends EntityNotFound(s"Group $id deleted") + final case class GroupIdAlreadyExists(id: Int) extends Exception with NoStackTrace object NotAMember extends Exception("Not a group member") with NoStackTrace @@ -38,4 +40,6 @@ object GroupErrors { case object BlockedByUser extends Exception with NoStackTrace case object NoPermission extends Exception with NoStackTrace + + case object CantLeaveGroup extends Exception with NoStackTrace } diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupOperations.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupOperations.scala index f9cca5f8c7..fdbb22efc5 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupOperations.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupOperations.scala @@ -122,6 +122,11 @@ private[group] sealed trait Commands extends UserAcl { GroupEnvelope(groupId) .withMakeHistoryShared(MakeHistoryShared(clientUserId, clientAuthId))).mapTo[SeqState] + def deleteGroup(groupId: Int, clientUserId: Int, clientAuthId: Long): Future[SeqState] = + (processorRegion.ref ? + GroupEnvelope(groupId) + .withDeleteGroup(DeleteGroup(clientUserId, clientAuthId))).mapTo[SeqState] + } private[group] sealed trait Queries { diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala index b6c9c45594..3e24dc56f1 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala @@ -50,6 +50,7 @@ object GroupProcessor { 20023 → classOf[GroupCommands.DismissUserAdmin], 20024 → classOf[GroupCommands.UpdateAdminSettings], 20025 → classOf[GroupCommands.MakeHistoryShared], + 20026 → classOf[GroupCommands.DeleteGroup], 21001 → classOf[GroupQueries.GetIntegrationToken], 21002 → classOf[GroupQueries.GetIntegrationTokenResponse], @@ -90,7 +91,8 @@ object GroupProcessor { 22018 → classOf[GroupEvents.ShortNameUpdated], 22019 → classOf[GroupEvents.AdminSettingsUpdated], 22020 → classOf[GroupEvents.AdminStatusChanged], - 22021 → classOf[GroupEvents.HistoryBecameShared] + 22021 → classOf[GroupEvents.HistoryBecameShared], + 22022 → classOf[GroupEvents.GroupDeleted] ) def persistenceIdFor(groupId: Int): String = s"Group-${groupId}" @@ -125,6 +127,7 @@ private[group] final class GroupProcessor case c: Create if state.isNotCreated ⇒ create(c) case _: Create ⇒ sender() ! Status.Failure(GroupIdAlreadyExists(groupId)) case _: GroupCommand if state.isNotCreated ⇒ sender() ! Status.Failure(GroupNotFound(groupId)) + case _: GroupCommand if state.isDeleted ⇒ sender() ! Status.Failure(GroupAlreadyDeleted(groupId)) // members actions case i: Invite ⇒ invite(i) @@ -146,15 +149,15 @@ private[group] final class GroupProcessor case t: TransferOwnership ⇒ transferOwnership(t) case s: UpdateAdminSettings ⇒ updateAdminSettings(s) case m: MakeHistoryShared ⇒ makeHistoryShared(m) - - // termination actions - case StopProcessor ⇒ context stop self - case ReceiveTimeout ⇒ context.parent ! ShardRegion.Passivate(stopMessage = StopProcessor) + case d: DeleteGroup ⇒ deleteGroup(d) // dialogs envelopes coming through group. case de: DialogEnvelope ⇒ groupPeerActor forward de.getAllFields.values.head + // actor's lifecycle + case StopProcessor ⇒ context stop self + case ReceiveTimeout ⇒ context.parent ! ShardRegion.Passivate(stopMessage = StopProcessor) } // TODO: add backoff @@ -165,6 +168,7 @@ private[group] final class GroupProcessor protected def handleQuery: PartialFunction[Any, Future[Any]] = { case _: GroupQuery if state.isNotCreated ⇒ FastFuture.failed(GroupNotFound(groupId)) + case _: GroupQuery if state.isDeleted ⇒ FastFuture.failed(GroupAlreadyDeleted(groupId)) case GetAccessHash() ⇒ getAccessHash case GetTitle() ⇒ getTitle case GetIntegrationToken(optClient) ⇒ getIntegrationToken(optClient) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala index e6de46c236..b6527fbbac 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala @@ -136,9 +136,9 @@ trait GroupQueryHandlers { canEditAdminList = Some(state.permissions.canEditAdmins(clientUserId)), canViewAdminList = Some(state.permissions.canViewAdmins(clientUserId)), canEditAdminSettings = Some(state.permissions.canEditAdminSettings(clientUserId)), - canInviteViaLink = None, - canDelete = None, - canLeave = None + canInviteViaLink = Some(state.permissions.canInviteViaLink(clientUserId)), + canDelete = Some(state.permissions.canDelete(clientUserId)), + canLeave = Some(state.permissions.canLeave(clientUserId)) ) ) } diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala index 77ec5178ab..fa51af8ce9 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala @@ -88,6 +88,7 @@ private[group] object GroupState { accessHash = 0L, adminSettings = AdminSettings.PlainDefault, bot = None, + deletedAt = None, //??? extensions = Map.empty @@ -120,6 +121,7 @@ private[group] final case class GroupState( accessHash: Long, adminSettings: AdminSettings, bot: Option[Bot], + deletedAt: Option[Instant], extensions: Map[Int, Array[Byte]] ) extends ProcessorState[GroupState] { @@ -148,6 +150,8 @@ private[group] final case class GroupState( val isCreated = createdAt.nonEmpty + val isDeleted = deletedAt.nonEmpty + //TODO: add on commit(not during recovery!) hook to make group with async members, when more than 100 def isAsyncMembers = groupType match { @@ -262,6 +266,8 @@ private[group] final case class GroupState( this.copy(adminSettings = AdminSettings.fromBitMask(bitMask)) case HistoryBecameShared(_, _) ⇒ this.copy(isHistoryShared = true) + case GroupDeleted(ts, _) ⇒ + this.copy(deletedAt = Some(ts)) // deprecated events case UserBecameAdmin(_, userId, _) ⇒ @@ -309,6 +315,12 @@ private[group] final case class GroupState( isAdmin(clientUserId) || (isMember(clientUserId) && adminSettings.canMembersInvite) + /** + * only owner and admins can invite via link + */ + def canInviteViaLink(clientUserId: Int) = + isOwner(clientUserId) || isAdmin(clientUserId) + /** * owner and admins can kick members */ @@ -344,5 +356,16 @@ private[group] final case class GroupState( // only owner can change admin settings def canEditAdminSettings(clientUserId: Int): Boolean = isOwner(clientUserId) + + // only owner can delete group + def canDelete(clientUserId: Int): Boolean = isOwner(clientUserId) + + /** + * for now, owner can't leave group. + * He can either transfer ownership and leave group + * or delete group completely. + */ + def canLeave(clientUserId: Int): Boolean = !isOwner(clientUserId) + } } diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/MemberCommandHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/MemberCommandHandlers.scala index e974152fbb..b4df43f301 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/MemberCommandHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/MemberCommandHandlers.scala @@ -10,6 +10,7 @@ import im.actor.api.rpc.messaging.{ ApiServiceMessage, UpdateMessage } import im.actor.concurrent.FutureExt import im.actor.server.acl.ACLUtils import im.actor.server.group.GroupCommands.{ Invite, Join, Kick, Leave } +import im.actor.server.group.GroupErrors.CantLeaveGroup import im.actor.server.group.GroupEvents.{ UserInvited, UserJoined, UserKicked, UserLeft } import im.actor.server.persist.{ GroupInviteTokenRepo, GroupUserRepo } import im.actor.server.sequence.{ Optimization, SeqState, SeqStateDate } @@ -336,6 +337,8 @@ private[group] trait MemberCommandHandlers extends GroupsImplicits { protected def leave(cmd: Leave): Unit = { if (state.nonMember(cmd.userId)) { sender() ! notMember + } else if (!state.permissions.canLeave(cmd.userId)) { + sender() ! Status.Failure(CantLeaveGroup) } else { persist(UserLeft(Instant.now, cmd.userId)) { evt ⇒ // no commit here. it will be after service message sent @@ -344,28 +347,32 @@ private[group] trait MemberCommandHandlers extends GroupsImplicits { val updateObsolete = UpdateGroupUserLeaveObsolete(groupId, cmd.userId, dateMillis, cmd.randomId) - // TODO: merge, they are almost identical - val leftUserUpdatesNew = - if (state.groupType.isChannel) List( - UpdateGroupCanViewMembersChanged(groupId, canViewMembers = false), - UpdateGroupCanEditInfoChanged(groupId, canEditGroup = false), - UpdateGroupCanEditUsernameChanged(groupId, canEditUsername = false), - UpdateGroupCanEditAdminsChanged(groupId, canAssignAdmins = false), - UpdateGroupCanViewAdminsChanged(groupId, canViewAdmins = false), - UpdateGroupCanEditAdminSettingsChanged(groupId, canEditAdminSettings = false), - UpdateGroupCanInviteMembersChanged(groupId, canInviteMembers = false) - ) - else List( + val leftUserUpdatesNew: Vector[Update] = { + val commonUpdates = Vector( + UpdateGroupCanSendMessagesChanged(groupId, canSendMessages = false), UpdateGroupCanEditInfoChanged(groupId, canEditGroup = false), UpdateGroupCanEditUsernameChanged(groupId, canEditUsername = false), UpdateGroupCanEditAdminsChanged(groupId, canAssignAdmins = false), UpdateGroupCanViewAdminsChanged(groupId, canViewAdmins = false), UpdateGroupCanEditAdminSettingsChanged(groupId, canEditAdminSettings = false), - UpdateGroupCanViewMembersChanged(groupId, canViewMembers = false), - UpdateGroupMembersUpdated(groupId, members = Vector.empty), - UpdateGroupCanInviteMembersChanged(groupId, canInviteMembers = false) + UpdateGroupCanInviteViaLink(groupId, canInviteViaLink = false), + UpdateGroupCanLeaveChanged(groupId, canLeaveChanged = false), + UpdateGroupCanDeleteChanged(groupId, canDeleteChanged = false) ) + if (state.groupType.isChannel) { + (UpdateGroupCanViewMembersChanged(groupId, canViewMembers = false) +: + commonUpdates) :+ + UpdateGroupCanInviteMembersChanged(groupId, canInviteMembers = false) + } else { + commonUpdates ++ Vector( + UpdateGroupCanViewMembersChanged(groupId, canViewMembers = false), + UpdateGroupMembersUpdated(groupId, members = Vector.empty), + UpdateGroupCanInviteMembersChanged(groupId, canInviteMembers = false) + ) + } + } + val membersUpdateNew = if (state.groupType.isChannel) { // if channel, or group is big enough UpdateGroupMembersCountChanged( @@ -485,30 +492,31 @@ private[group] trait MemberCommandHandlers extends GroupsImplicits { val updateObsolete = UpdateGroupUserKickObsolete(groupId, cmd.kickedUserId, cmd.kickerUserId, dateMillis, cmd.randomId) - // TODO: merge, they are almost identical - val kickedUserUpdatesNew: List[Update] = - if (state.groupType.isChannel) List( + val kickedUserUpdatesNew: Vector[Update] = { + val commonUpdates = Vector( UpdateGroupCanViewMembersChanged(groupId, canViewMembers = false), + UpdateGroupCanSendMessagesChanged(groupId, canSendMessages = false), UpdateGroupCanEditInfoChanged(groupId, canEditGroup = false), UpdateGroupCanEditUsernameChanged(groupId, canEditUsername = false), UpdateGroupCanEditAdminsChanged(groupId, canAssignAdmins = false), UpdateGroupCanViewAdminsChanged(groupId, canViewAdmins = false), UpdateGroupCanEditAdminSettingsChanged(groupId, canEditAdminSettings = false), UpdateGroupCanInviteMembersChanged(groupId, canInviteMembers = false), - UpdateGroupMemberChanged(groupId, isMember = false) - ) - else List( - UpdateGroupCanViewMembersChanged(groupId, canViewMembers = false), - UpdateGroupCanEditInfoChanged(groupId, canEditGroup = false), - UpdateGroupCanEditUsernameChanged(groupId, canEditUsername = false), - UpdateGroupCanEditAdminsChanged(groupId, canAssignAdmins = false), - UpdateGroupCanViewAdminsChanged(groupId, canViewAdmins = false), - UpdateGroupCanEditAdminSettingsChanged(groupId, canEditAdminSettings = false), - UpdateGroupCanInviteMembersChanged(groupId, canInviteMembers = false), - UpdateGroupMembersUpdated(groupId, members = Vector.empty), - UpdateGroupMemberChanged(groupId, isMember = false) + UpdateGroupCanInviteViaLink(groupId, canInviteViaLink = false), + UpdateGroupCanLeaveChanged(groupId, canLeaveChanged = false), + UpdateGroupCanDeleteChanged(groupId, canDeleteChanged = false) ) + if (state.groupType.isChannel) { + commonUpdates :+ UpdateGroupMemberChanged(groupId, isMember = false) + } else { + commonUpdates ++ Vector( + UpdateGroupMembersUpdated(groupId, members = Vector.empty), + UpdateGroupMemberChanged(groupId, isMember = false) + ) + } + } + val membersUpdateNew: Update = if (newState.groupType.isChannel) { // if channel, or group is big enough UpdateGroupMembersCountChanged( @@ -618,7 +626,6 @@ private[group] trait MemberCommandHandlers extends GroupsImplicits { // Updates that will be sent to user, when he enters group. // Helps clients that have this group to refresh it's data. - // TODO: review when channels will be added private def refreshGroupUpdates(newState: GroupState, userId: Int): List[Update] = List( UpdateGroupMemberChanged(groupId, isMember = true), UpdateGroupAboutChanged(groupId, newState.about), @@ -626,8 +633,17 @@ private[group] trait MemberCommandHandlers extends GroupsImplicits { UpdateGroupTopicChanged(groupId, newState.topic), UpdateGroupTitleChanged(groupId, newState.title), UpdateGroupOwnerChanged(groupId, newState.ownerUserId), - UpdateGroupCanViewMembersChanged(groupId, canViewMembers = newState.permissions.canViewMembers(userId)), - UpdateGroupCanInviteMembersChanged(groupId, canInviteMembers = true) // TODO: figure out right value + UpdateGroupCanSendMessagesChanged(groupId, newState.permissions.canSendMessage(userId)), + UpdateGroupCanViewMembersChanged(groupId, newState.permissions.canViewMembers(userId)), + UpdateGroupCanInviteMembersChanged(groupId, newState.permissions.canInvitePeople(userId)), + UpdateGroupCanEditInfoChanged(groupId, newState.permissions.canEditInfo(userId)), + UpdateGroupCanEditUsernameChanged(groupId, newState.permissions.canEditShortName(userId)), + UpdateGroupCanEditAdminsChanged(groupId, newState.permissions.canEditAdmins(userId)), + UpdateGroupCanViewAdminsChanged(groupId, newState.permissions.canViewAdmins(userId)), + UpdateGroupCanInviteViaLink(groupId, newState.permissions.canInviteViaLink(userId)), + UpdateGroupCanLeaveChanged(groupId, newState.permissions.canLeave(userId)), + UpdateGroupCanDeleteChanged(groupId, newState.permissions.canDelete(userId)), + UpdateGroupCanEditAdminSettingsChanged(groupId, newState.permissions.canEditAdminSettings(userId)) // UpdateGroupExtChanged(groupId, newState.extension) //TODO: figure out and fix // if(bigGroup) UpdateGroupMembersCountChanged(groupId, newState.extension) ) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/sequence/Optimization.scala b/actor-server/actor-core/src/main/scala/im/actor/server/sequence/Optimization.scala index 7f517876c4..e74b725349 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/sequence/Optimization.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/sequence/Optimization.scala @@ -50,7 +50,10 @@ object Optimization extends MessageParsing { UpdateGroupCanEditUsernameChanged.header, UpdateGroupCanEditAdminsChanged.header, UpdateGroupCanViewAdminsChanged.header, - UpdateGroupCanEditAdminSettingsChanged.header + UpdateGroupCanEditAdminSettingsChanged.header, + UpdateGroupCanInviteViaLink.header, + UpdateGroupCanLeaveChanged.header, + UpdateGroupCanDeleteChanged.header ) if (deliveryTag == GroupV2) emptyUpdate diff --git a/actor-server/actor-rpc-api/src/main/scala/im/actor/api/rpc/PeerHelpers.scala b/actor-server/actor-rpc-api/src/main/scala/im/actor/api/rpc/PeerHelpers.scala index 6d67738c4e..af4797e4d9 100644 --- a/actor-server/actor-rpc-api/src/main/scala/im/actor/api/rpc/PeerHelpers.scala +++ b/actor-server/actor-rpc-api/src/main/scala/im/actor/api/rpc/PeerHelpers.scala @@ -7,7 +7,7 @@ import im.actor.api.rpc.CommonRpcErrors.InvalidAccessHash import im.actor.api.rpc.peers._ import im.actor.server.acl.ACLUtils._ import im.actor.server.db.DbExtension -import im.actor.server.group.GroupErrors.GroupNotFound +import im.actor.server.group.GroupErrors.{ GroupAlreadyDeleted, GroupNotFound } import im.actor.server.user.UserErrors.UserNotFound import slick.dbio.DBIO @@ -83,8 +83,9 @@ object PeerHelpers { } private def handleNotFound: PartialFunction[Throwable, RpcError] = { - case _: UserNotFound ⇒ CommonRpcErrors.UserNotFound - case _: GroupNotFound ⇒ CommonRpcErrors.GroupNotFound - case e ⇒ throw e + case _: UserNotFound ⇒ CommonRpcErrors.UserNotFound + case _: GroupNotFound ⇒ CommonRpcErrors.GroupNotFound + case _: GroupAlreadyDeleted ⇒ CommonRpcErrors.GroupDeleted + case e ⇒ throw e } } diff --git a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/files/FilesServiceImpl.scala b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/files/FilesServiceImpl.scala index ec63b7cd32..f75bfaf914 100644 --- a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/files/FilesServiceImpl.scala +++ b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/files/FilesServiceImpl.scala @@ -6,7 +6,7 @@ import java.time.temporal.ChronoUnit import akka.actor._ import akka.http.scaladsl.util.FastFuture import cats.data.Xor -import im.actor.api.rpc.CommonRpcErrors.IntenalError +import im.actor.api.rpc.CommonRpcErrors.InternalError import im.actor.api.rpc.FileRpcErrors.UnsupportedSignatureAlgorithm import im.actor.api.rpc._ import im.actor.api.rpc.files._ diff --git a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupRpcErrors.scala b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupRpcErrors.scala index 83e425bab7..ecf61be123 100644 --- a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupRpcErrors.scala +++ b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupRpcErrors.scala @@ -18,6 +18,7 @@ object GroupRpcErrors { val InvalidInviteToken = RpcError(403, "INVALID_INVITE_TOKEN", "Invalid invite token!", false, None) val InvalidInviteGroup = RpcError(403, "INVALID_INVITE_GROUP", "Invalid group name provided!", false, None) val GroupNotPublic = RpcError(400, "GROUP_IS_NOT_PUBLIC", "The group is not public.", false, None) + val CantLeaveGroup = RpcError(403, "CANT_LEAVE_GROUP", "You can't leave this group!", false, None) val InvalidShortName = RpcError(400, "GROUP_SHORT_NAME_INVALID", "Invalid group short name. Valid short name should contain from 5 to 32 characters, and may consist of latin characters, numbers and underscores", false, None) val ShortNameTaken = RpcError(400, "GROUP_SHORT_NAME_TAKEN", "This short name already belongs to other user or group, we are sorry!", false, None) diff --git a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupsServiceImpl.scala b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupsServiceImpl.scala index fc654f9019..7e03397d99 100644 --- a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupsServiceImpl.scala +++ b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupsServiceImpl.scala @@ -16,7 +16,7 @@ import im.actor.api.rpc.users.ApiUser import im.actor.concurrent.FutureExt import im.actor.server.acl.ACLUtils import im.actor.server.db.DbExtension -import im.actor.server.file.{ FileErrors, FileStorageAdapter, FileStorageExtension, ImageUtils } +import im.actor.server.file.{ FileErrors, ImageUtils } import im.actor.server.group._ import im.actor.server.model.GroupInviteToken import im.actor.server.names.GlobalNamesStorageKeyValueStorage @@ -459,7 +459,23 @@ final class GroupsServiceImpl(groupInviteConfig: GroupInviteConfig)(implicit act } } - protected def doHandleDeleteGroup(groupPeer: ApiGroupOutPeer, clientData: ClientData): Future[HandlerResult[ResponseSeq]] = ??? + protected def doHandleDeleteGroup(groupPeer: ApiGroupOutPeer, clientData: ClientData): Future[HandlerResult[ResponseSeq]] = + authorized(clientData) { client ⇒ + withGroupOutPeer(groupPeer) { + for { + SeqState(seq, state) ← groupExt.deleteGroup(groupPeer.groupId, client.userId, client.authId) + } yield Ok(ResponseSeq(seq, state.toByteArray)) + } + } + + protected def doHandleShareHistory(groupPeer: ApiGroupOutPeer, clientData: ClientData): Future[HandlerResult[ResponseSeq]] = + authorized(clientData) { client ⇒ + withGroupOutPeer(groupPeer) { + for { + SeqState(seq, state) ← groupExt.makeHistoryShared(groupPeer.groupId, client.userId, client.authId) + } yield Ok(ResponseSeq(seq, state.toByteArray)) + } + } private def usersOrPeers(userIds: Vector[Int], stripEntities: Boolean)(implicit client: AuthorizedClientData): Future[(Vector[ApiUser], Vector[ApiUserOutPeer])] = if (stripEntities) { @@ -594,6 +610,7 @@ final class GroupsServiceImpl(groupInviteConfig: GroupInviteConfig)(implicit act case GroupErrors.InvalidShortName ⇒ GroupRpcErrors.InvalidShortName case GroupErrors.ShortNameTaken ⇒ GroupRpcErrors.ShortNameTaken case GroupErrors.NoPermission ⇒ GroupRpcErrors.NoPermission + case GroupErrors.CantLeaveGroup ⇒ GroupRpcErrors.CantLeaveGroup } } diff --git a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/messaging/HistoryHandlers.scala b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/messaging/HistoryHandlers.scala index 18008066e3..affe506234 100644 --- a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/messaging/HistoryHandlers.scala +++ b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/messaging/HistoryHandlers.scala @@ -46,6 +46,7 @@ trait HistoryHandlers { } } + // FIXME: handle clear chat for groups with shared history override def doHandleClearChat(peer: ApiOutPeer, clientData: ClientData): Future[HandlerResult[ResponseSeq]] = authorized(clientData) { implicit client ⇒ val update = UpdateChatClear(peer.asPeer) diff --git a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/messaging/MessagingHandlers.scala b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/messaging/MessagingHandlers.scala index 7715069165..83a27e0f76 100644 --- a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/messaging/MessagingHandlers.scala +++ b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/messaging/MessagingHandlers.scala @@ -3,7 +3,7 @@ package im.actor.server.api.rpc.service.messaging import akka.http.scaladsl.util.FastFuture import akka.util.Timeout import cats.data.Xor -import im.actor.api.rpc.CommonRpcErrors.IntenalError +import im.actor.api.rpc.CommonRpcErrors.InternalError import im.actor.api.rpc.{ CommonRpcErrors, _ } import im.actor.api.rpc.messaging._ import im.actor.api.rpc.misc._ @@ -71,7 +71,7 @@ private[messaging] trait MessagingHandlers extends PeersImplicits (for { histMessage ← fromFutureOption(NotAllowedToEdit)(getEditableHistoryMessage(peer, randomId)) _ ← fromBoolean(NotInTimeWindow)(inTimeWindow(histMessage.date.getMillis)) - apiMessage ← fromXor((e: Any) ⇒ IntenalError)(Xor.fromEither(parseMessage(histMessage.messageContentData))) + apiMessage ← fromXor((e: Any) ⇒ InternalError)(Xor.fromEither(parseMessage(histMessage.messageContentData))) _ ← fromBoolean(NotTextMessage)(apiMessage match { case _: ApiTextMessage ⇒ true case _ ⇒ false From fb632850a29e821e534960694d187615a34fc297 Mon Sep 17 00:00:00 2001 From: rockjam Date: Wed, 20 Jul 2016 18:14:30 +0300 Subject: [PATCH 161/414] fix(server:groups): take group type from event --- .../scala/im/actor/server/group/AdminCommandHandlers.scala | 3 +++ .../src/main/scala/im/actor/server/group/GroupState.scala | 5 +++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/AdminCommandHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/AdminCommandHandlers.scala index e3dda1402f..566d29f3c9 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/AdminCommandHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/AdminCommandHandlers.scala @@ -366,6 +366,9 @@ private[group] trait AdminCommandHandlers extends GroupsImplicits { UpdateGroupMembersUpdated(groupId, members = Vector.empty) ) + // TODO: add UpdateIsDeleted + // TODO: for old API updates as in leacve + val result: Future[SeqState] = for { _ ← db.run(HistoryMessageRepo.deleteAll(cmd.clientUserId, apiGroupPeer.asModel)) _ ← Future.traverse(deleteGroupMembersUpdates) { update ⇒ diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala index fa51af8ce9..c289fdd382 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala @@ -167,6 +167,7 @@ private[group] final case class GroupState( override def updated(e: Event): GroupState = e match { case evt: Created ⇒ + val typeOfGroup = evt.typ.getOrElse(GroupType.General) this.copy( id = evt.groupId, createdAt = Some(evt.ts), @@ -177,7 +178,7 @@ private[group] final case class GroupState( avatar = None, topic = None, shortName = None, - groupType = evt.typ.getOrElse(GroupType.General), + groupType = typeOfGroup, isHidden = evt.isHidden getOrElse false, isHistoryShared = evt.isHistoryShared getOrElse false, members = ( @@ -194,7 +195,7 @@ private[group] final case class GroupState( invitedUserIds = evt.userIds.filterNot(_ == evt.creatorUserId).toSet, accessHash = evt.accessHash, adminSettings = - if (groupType.isChannel) AdminSettings.ChannelsDefault + if (typeOfGroup.isChannel) AdminSettings.ChannelsDefault else AdminSettings.PlainDefault, bot = None, extensions = (evt.extensions map { //TODO: validate is it right? From 9abc6a8e1aef924f5248bfe95154b8ee298794a9 Mon Sep 17 00:00:00 2001 From: rockjam Date: Wed, 20 Jul 2016 22:48:09 +0300 Subject: [PATCH 162/414] fix(server:groups): forbid to clear chats with shared history --- .../service/messaging/HistoryHandlers.scala | 40 ++++++++++--------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/messaging/HistoryHandlers.scala b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/messaging/HistoryHandlers.scala index affe506234..1735f482cd 100644 --- a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/messaging/HistoryHandlers.scala +++ b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/messaging/HistoryHandlers.scala @@ -46,28 +46,30 @@ trait HistoryHandlers { } } - // FIXME: handle clear chat for groups with shared history override def doHandleClearChat(peer: ApiOutPeer, clientData: ClientData): Future[HandlerResult[ResponseSeq]] = - authorized(clientData) { implicit client ⇒ - val update = UpdateChatClear(peer.asPeer) + authorized(clientData) { client ⇒ + val canClearChat = peer.`type` match { + case ApiPeerType.Private | ApiPeerType.EncryptedPrivate ⇒ + FastFuture.successful(true) + case ApiPeerType.Group ⇒ + groupExt.isHistoryShared(peer.id) map (!_) + } - val action = (for { - _ ← fromDBIOBoolean(CommonRpcErrors.forbidden("Clearing of public chats is forbidden")) { - if (peer.`type` == ApiPeerType.Private) { - DBIO.successful(true) - } else { - DBIO.from(groupExt.isHistoryShared(peer.id)) flatMap (isHistoryShared ⇒ DBIO.successful(!isHistoryShared)) - } + for { + canDelete ← canClearChat + SeqState(seq, state) ← if (canDelete) { + for { + _ ← db.run(HistoryMessageRepo.deleteAll(client.userId, peer.asModel)) + seqState ← seqUpdExt.deliverClientUpdate( + client.userId, + client.authId, + update = UpdateChatClear(peer.asPeer) + ) + } yield seqState + } else { + FastFuture.successful(Error(CommonRpcErrors.forbidden("Can't clear chat with shared history"))) } - _ ← fromDBIO(HistoryMessageRepo.deleteAll(client.userId, peer.asModel)) - seqState ← fromFuture(seqUpdExt.deliverClientUpdate( - client.userId, - client.authId, - update, - pushRules = seqUpdExt.pushRules(isFat = false, None) - )) - } yield ResponseSeq(seqState.seq, seqState.state.toByteArray)).value - db.run(action) + } yield Ok(ResponseSeq(seq, state.toByteArray)) } override def doHandleDeleteChat(peer: ApiOutPeer, clientData: ClientData): Future[HandlerResult[ResponseSeq]] = { From 4376dfcbc900fd4563be3cc6713ccd2d8cbd46fa Mon Sep 17 00:00:00 2001 From: rockjam Date: Wed, 20 Jul 2016 23:02:31 +0300 Subject: [PATCH 163/414] fix(server:groups): push leave update to client with old group API --- .../server/group/AdminCommandHandlers.scala | 33 +++++++++++++++++-- .../actor/server/group/GroupProcessor.scala | 1 + 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/AdminCommandHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/AdminCommandHandlers.scala index 566d29f3c9..50fb910bff 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/AdminCommandHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/AdminCommandHandlers.scala @@ -14,7 +14,7 @@ import im.actor.server.acl.ACLUtils import im.actor.server.group.GroupCommands.{ DeleteGroup, DismissUserAdmin, MakeHistoryShared, MakeUserAdmin, RevokeIntegrationToken, RevokeIntegrationTokenAck, TransferOwnership, UpdateAdminSettings, UpdateAdminSettingsAck } import im.actor.server.group.GroupErrors.{ NotAMember, NotAdmin, UserAlreadyAdmin, UserAlreadyNotAdmin } import im.actor.server.group.GroupEvents.{ AdminSettingsUpdated, AdminStatusChanged, GroupDeleted, HistoryBecameShared, IntegrationTokenRevoked, OwnerChanged } -import im.actor.server.persist.{ GroupBotRepo, GroupUserRepo, HistoryMessageRepo } +import im.actor.server.persist.{ GroupBotRepo, GroupInviteTokenRepo, GroupUserRepo, HistoryMessageRepo } import im.actor.server.sequence.{ SeqState, SeqStateDate } import scala.concurrent.Future @@ -346,6 +346,10 @@ private[group] trait AdminCommandHandlers extends GroupsImplicits { persist(GroupDeleted(Instant.now, cmd.clientUserId)) { evt ⇒ val newState = commit(evt) + val dateMillis = evt.ts.toEpochMilli + val randomId = ACLUtils.randomLong() + + // TODO: add UpdateIsDeleted val deleteGroupMembersUpdates: Vector[Update] = Vector( UpdateGroupCanSendMessagesChanged(groupId, canSendMessages = false), UpdateGroupCanViewMembersChanged(groupId, canViewMembers = false), @@ -366,11 +370,34 @@ private[group] trait AdminCommandHandlers extends GroupsImplicits { UpdateGroupMembersUpdated(groupId, members = Vector.empty) ) - // TODO: add UpdateIsDeleted - // TODO: for old API updates as in leacve + //TODO: remove deprecated. GroupInviteTokenRepo don't have replacement yet. + newState.memberIds foreach { userId ⇒ + db.run( + for { + _ ← GroupUserRepo.delete(groupId, userId) + _ ← GroupInviteTokenRepo.revoke(groupId, userId) + } yield () + ) + } val result: Future[SeqState] = for { _ ← db.run(HistoryMessageRepo.deleteAll(cmd.clientUserId, apiGroupPeer.asModel)) + + /////////////////////////// + // Groups V1 API updates // + /////////////////////////// + + // push all members updates about other members left group + _ ← FutureExt.ftraverse(newState.memberIds.toSeq) { userId ⇒ + seqUpdExt.broadcastPeopleUpdate( + userIds = newState.memberIds - userId, + update = UpdateGroupUserLeaveObsolete(groupId, userId, dateMillis, randomId) + ) + } + + /////////////////////////// + // Groups V2 API updates // + /////////////////////////// _ ← Future.traverse(deleteGroupMembersUpdates) { update ⇒ seqUpdExt.broadcastPeopleUpdate(newState.memberIds, update) } diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala index 3e24dc56f1..faf98a19a5 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala @@ -21,6 +21,7 @@ import im.actor.server.user.UserExtension import scala.concurrent.duration._ import scala.concurrent.{ ExecutionContext, Future } +//TODO: maybe add dateMillis trait GroupEvent extends TaggedEvent { val ts: Instant From a1bac369061001f62a1f3b206476340d49753584 Mon Sep 17 00:00:00 2001 From: rockjam Date: Wed, 20 Jul 2016 23:08:20 +0300 Subject: [PATCH 164/414] chore(server): update actor.json --- .../actor-core/src/main/actor-api/actor.json | 122 ++++++------------ 1 file changed, 42 insertions(+), 80 deletions(-) diff --git a/actor-server/actor-core/src/main/actor-api/actor.json b/actor-server/actor-core/src/main/actor-api/actor.json index 2fe4b769ee..30da5aee54 100644 --- a/actor-server/actor-core/src/main/actor-api/actor.json +++ b/actor-server/actor-core/src/main/actor-api/actor.json @@ -7936,6 +7936,12 @@ "category": "full", "description": " Can user send messages. Default is equals isMember for Group and false for others." }, + { + "type": "reference", + "argument": "isDeleted", + "category": "full", + "description": " Is this group deleted" + }, { "type": "reference", "argument": "ext", @@ -8053,6 +8059,14 @@ "id": 26, "name": "canSendMessage" }, + { + "type": { + "type": "opt", + "childType": "bool" + }, + "id": 27, + "name": "isDeleted" + }, { "type": { "type": "opt", @@ -9320,6 +9334,32 @@ ] } }, + { + "type": "update", + "content": { + "name": "GroupDeleted", + "header": 2658, + "doc": [ + "Update about group deleted", + { + "type": "reference", + "argument": "groupId", + "category": "full", + "description": " Group Id" + } + ], + "attributes": [ + { + "type": { + "type": "alias", + "childType": "groupId" + }, + "id": 1, + "name": "groupId" + } + ] + } + }, { "type": "comment", "content": " " @@ -12693,39 +12733,9 @@ }, { "type": "reference", - "argument": "title", - "category": "full", - "description": " Peer title" - }, - { - "type": "reference", - "argument": "description", + "argument": "optMatchString", "category": "full", "description": " Description" - }, - { - "type": "reference", - "argument": "membersCount", - "category": "full", - "description": " Members count" - }, - { - "type": "reference", - "argument": "dateCreated", - "category": "full", - "description": " Group Creation Date" - }, - { - "type": "reference", - "argument": "creator", - "category": "full", - "description": " Group Creator uid" - }, - { - "type": "reference", - "argument": "isPublic", - "category": "full", - "description": " Is group public" } ], "attributes": [ @@ -12737,61 +12747,13 @@ "id": 1, "name": "peer" }, - { - "type": "string", - "id": 2, - "name": "title" - }, { "type": { "type": "opt", "childType": "string" }, "id": 3, - "name": "description" - }, - { - "type": { - "type": "opt", - "childType": "int32" - }, - "id": 4, - "name": "membersCount" - }, - { - "type": { - "type": "opt", - "childType": { - "type": "alias", - "childType": "date" - } - }, - "id": 5, - "name": "dateCreated" - }, - { - "type": { - "type": "opt", - "childType": "int32" - }, - "id": 6, - "name": "creator" - }, - { - "type": { - "type": "opt", - "childType": "bool" - }, - "id": 7, - "name": "isPublic" - }, - { - "type": { - "type": "opt", - "childType": "bool" - }, - "id": 8, - "name": "isJoined" + "name": "optMatchString" } ] } From 8f13734da75675d1b8f816a937b3a7ba328e7b88 Mon Sep 17 00:00:00 2001 From: rockjam Date: Thu, 21 Jul 2016 02:18:38 +0300 Subject: [PATCH 165/414] feat(server:groups,search): isDeleted and update in groups; search users and groups by global name prefix --- .../server/group/AdminCommandHandlers.scala | 3 +- .../server/group/GroupQueryHandlers.scala | 3 +- .../server/names/GlobalNamesStorage.scala | 35 +++++ .../im/actor/server/user/UserOperations.scala | 8 +- .../im/actor/server/persist/UserRepo.scala | 20 ++- .../service/search/SearchServiceImpl.scala | 120 +++++++++++++----- 6 files changed, 146 insertions(+), 43 deletions(-) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/AdminCommandHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/AdminCommandHandlers.scala index 50fb910bff..0dbd1666bf 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/AdminCommandHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/AdminCommandHandlers.scala @@ -367,7 +367,8 @@ private[group] trait AdminCommandHandlers extends GroupsImplicits { if (newState.groupType.isChannel) UpdateGroupMembersCountChanged(groupId, membersCount = 0) else - UpdateGroupMembersUpdated(groupId, members = Vector.empty) + UpdateGroupMembersUpdated(groupId, members = Vector.empty), + UpdateGroupDeleted(groupId) ) //TODO: remove deprecated. GroupInviteTokenRepo don't have replacement yet. diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala index b6527fbbac..cfde4c1f59 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala @@ -108,7 +108,8 @@ trait GroupQueryHandlers { case Channel ⇒ ApiGroupType.CHANNEL case General | Unrecognized(_) ⇒ ApiGroupType.GROUP }), - canSendMessage = Some(state.permissions.canSendMessage(clientUserId)) + canSendMessage = Some(state.permissions.canSendMessage(clientUserId)), + isDeleted = Some(state.isDeleted) ) ) } diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/names/GlobalNamesStorage.scala b/actor-server/actor-core/src/main/scala/im/actor/server/names/GlobalNamesStorage.scala index d6cae1bc9e..dc766a5ef7 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/names/GlobalNamesStorage.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/names/GlobalNamesStorage.scala @@ -34,6 +34,41 @@ final class GlobalNamesStorageKeyValueStorage(implicit system: ActorSystem) { case GlobalNameOwner(OwnerType.User, userId) ⇒ userId }) + /** + * Search groups(id -> global name) by global name prefix + * Looks only in GlobalNamesStorage + */ + def groupIdsByPrefix(namePrefix: String): Future[IndexedSeq[(Int, String)]] = { + conn.run(GlobalNamesStorage.getByPrefix(namePrefix)) map { searchResults ⇒ + searchResults flatMap { + case (fullName, bytes) ⇒ + Some(GlobalNameOwner.parseFrom(bytes)) filter (_.ownerType.isGroup) map (o ⇒ o.ownerId → fullName) + } + } + } + + /** + * Search users(id -> global name) by global name prefix + * Looks in both GlobalNamesStorage and UserRepo(compatibility mode) + */ + def userIdsByPrefix(namePrefix: String): Future[IndexedSeq[(Int, String)]] = { + val kvSearch = conn.run(GlobalNamesStorage.getByPrefix(namePrefix)) map { searchResults ⇒ + searchResults flatMap { + case (fullName, bytes) ⇒ + Some(GlobalNameOwner.parseFrom(bytes)) filter (_.ownerType.isUser) map (o ⇒ o.ownerId → fullName) + } + } + val compatSearch = db.run(UserRepo.findByNicknamePrefix(namePrefix)) map { users ⇒ + users flatMap { user ⇒ + user.nickname map (user.id → _) + } + } + for { + kv ← kvSearch + compat ← compatSearch + } yield kv ++ compat + } + def getGroupId(name: String): Future[Option[Int]] = getOwner(name) map (_.collect { case GlobalNameOwner(OwnerType.Group, groupId) ⇒ groupId diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/user/UserOperations.scala b/actor-server/actor-core/src/main/scala/im/actor/server/user/UserOperations.scala index a8ca2f0903..56cd6bb307 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/user/UserOperations.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/user/UserOperations.scala @@ -11,6 +11,7 @@ import im.actor.server.auth.DeviceInfo import im.actor.server.bots.BotCommand import im.actor.server.db.DbExtension import im.actor.server.file.Avatar +import im.actor.server.names.GlobalNamesStorageKeyValueStorage import im.actor.server.persist.UserRepo import im.actor.server.pubsub.PubSubExtension import im.actor.server.sequence.{ SeqState, SeqUpdatesExtension } @@ -144,6 +145,7 @@ private[user] sealed trait Queries { implicit val system: ActorSystem import system.dispatcher val log: LoggingAdapter + private lazy val globalNamesStorage = new GlobalNamesStorageKeyValueStorage implicit val timeout: Timeout @@ -192,7 +194,11 @@ private[user] sealed trait Queries { def isAdmin(userId: Int): Future[Boolean] = (viewRegion.ref ? IsAdmin(userId)).mapTo[IsAdminResponse].map(_.isAdmin) - def findUserIds(query: String): Future[Seq[Int]] = DbExtension(system).db.run(UserRepo.findIds(query)) + def findUserIds(query: String): Future[Seq[Int]] = + for { + byPhoneAndEmail ← DbExtension(system).db.run(UserRepo.findIds(query)) + byNickname ← globalNamesStorage.getUserId(query) + } yield byPhoneAndEmail ++ byNickname } private[user] sealed trait AuthCommands { diff --git a/actor-server/actor-persist/src/main/scala/im/actor/server/persist/UserRepo.scala b/actor-server/actor-persist/src/main/scala/im/actor/server/persist/UserRepo.scala index efea1aa4e7..35391e0661 100644 --- a/actor-server/actor-persist/src/main/scala/im/actor/server/persist/UserRepo.scala +++ b/actor-server/actor-persist/src/main/scala/im/actor/server/persist/UserRepo.scala @@ -40,11 +40,13 @@ object UserRepo { val byIdC = Compiled(byId _) val nameByIdC = Compiled(nameById _) - private def byNickname(nickname: Rep[String]) = users filter (_.nickname.toLowerCase === nickname.toLowerCase) - private def idsByNickname(nickname: Rep[String]) = byNickname(nickname).map(_.id) + private def byNickname(nickname: Rep[String]) = + users filter (_.nickname.toLowerCase === nickname.toLowerCase) + private def byNicknamePrefix(nickPrefix: Rep[String]) = + users filter (_.nickname.toLowerCase.like(nickPrefix.toLowerCase)) private val byNicknameC = Compiled(byNickname _) - private val idsByNicknameC = Compiled(idsByNickname _) + private val byNicknamePrefixC = Compiled(byNicknamePrefix _) def byPhone(phone: Rep[Long]) = (for { phones ← UserPhoneRepo.phones.filter(_.number === phone) @@ -107,24 +109,30 @@ object UserRepo { users.filter(_.id inSet ids).map(u ⇒ (u.id, u.accessSalt)).result @deprecated("user GlobalNamesStorageKeyValueStorage instead", "2016-07-17") - def findByNickname(query: String) = { + def findByNickname(query: String): DBIO[Option[User]] = { val nickname = if (query.startsWith("@")) query.drop(1) else query byNicknameC(nickname).result.headOption } + @deprecated("user GlobalNamesStorageKeyValueStorage instead", "2016-07-17") + def findByNicknamePrefix(query: String): DBIO[Seq[User]] = { + val nickname: String = + if (query.startsWith("@")) query.drop(1) else query + byNicknamePrefixC(nickname).result + } + def findIdsByEmail(email: String) = idsByEmailC(email).result.headOption def findIds(query: String)(implicit ec: ExecutionContext) = for { e ← idsByEmailC(query).result - n ← idsByNicknameC(query).result p ← PhoneNumberUtils.normalizeStr(query) .headOption .map(idByPhoneC(_).result) .getOrElse(DBIO.successful(Nil)) - } yield e ++ n ++ p + } yield e ++ p @deprecated("user GlobalNamesStorageKeyValueStorage instead", "2016-07-17") def setNickname(userId: Int, nickname: Option[String]) = diff --git a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/search/SearchServiceImpl.scala b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/search/SearchServiceImpl.scala index 82c3f34db7..1bff78bb62 100644 --- a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/search/SearchServiceImpl.scala +++ b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/search/SearchServiceImpl.scala @@ -12,8 +12,8 @@ import im.actor.concurrent.FutureExt import im.actor.server.db.DbExtension import im.actor.server.dialog.DialogExtension import im.actor.server.group.{ GroupExtension, GroupUtils } +import im.actor.server.names.GlobalNamesStorageKeyValueStorage import im.actor.server.persist.contact.UserContactRepo -import im.actor.server.persist.GroupRepo import im.actor.server.user.UserExtension import scala.concurrent.{ ExecutionContext, Future } @@ -21,9 +21,12 @@ import scala.concurrent.{ ExecutionContext, Future } class SearchServiceImpl(implicit system: ActorSystem) extends SearchService { override implicit protected val ec: ExecutionContext = system.dispatcher - protected val db = DbExtension(system).db - protected val userExt = UserExtension(system) - protected val groupExt = GroupExtension(system) + private val db = DbExtension(system).db + private val userExt = UserExtension(system) + private val groupExt = GroupExtension(system) + private val globalNamesStorage = new GlobalNamesStorageKeyValueStorage + + private val EmptySearch = ResponsePeerSearch(Vector.empty, Vector.empty, Vector.empty, Vector.empty, Vector.empty) override def doHandlePeerSearch( query: IndexedSeq[ApiSearchCondition], @@ -38,9 +41,16 @@ class SearchServiceImpl(implicit system: ActorSystem) extends SearchService { } texts.toList match { - case text :: Nil ⇒ searchResult(peerTypes.toVector, Some(text), optimizations) - case Nil ⇒ searchResult(peerTypes.toVector, None, optimizations) - case _ ⇒ FastFuture.successful(Error(RpcError(400, "INVALID_QUERY", "Invalid query.", canTryAgain = false, None))) + case text :: Nil if text.length < 3 ⇒ + FastFuture.successful(Ok(EmptySearch)) + case text :: Nil ⇒ + val tps = if (peerTypes.isEmpty) + Set(ApiSearchPeerType.Public, ApiSearchPeerType.Contacts, ApiSearchPeerType.Groups) + else + peerTypes + searchResult(tps.toVector, Some(text), optimizations) + case Nil ⇒ searchResult(peerTypes.toVector, None, optimizations) + case _ ⇒ FastFuture.successful(Error(RpcError(400, "INVALID_QUERY", "Invalid query.", canTryAgain = false, None))) } } } @@ -73,6 +83,7 @@ class SearchServiceImpl(implicit system: ActorSystem) extends SearchService { case ApiPeerType.Group ⇒ (gids :+ pid, uids) } } + // TODO: make like here: im.actor.server.api.rpc.service.groups.GroupsServiceImpl.usersOrPeers (groups, users) ← GroupUtils.getGroupsUsers(groupIds, userIds, client.userId, client.authId) } yield { val stripEntities = optimizations.contains(ApiUpdateOptimization.STRIP_ENTITIES) @@ -86,6 +97,8 @@ class SearchServiceImpl(implicit system: ActorSystem) extends SearchService { } } + type PeerAndMatchString = (ApiPeer, String) + private def search(pt: ApiSearchPeerType.Value, text: Option[String])(implicit clientData: AuthorizedClientData): Future[IndexedSeq[ApiPeerSearchResult]] = { pt match { case ApiSearchPeerType.Contacts ⇒ @@ -94,39 +107,85 @@ class SearchServiceImpl(implicit system: ActorSystem) extends SearchService { } yield users map result case ApiSearchPeerType.Groups ⇒ for { - groups ← searchGroups(text) - } yield groups map (result(_, isPublic = false)) + groups ← searchLocalGroups(text) + } yield groups map result + // global search case ApiSearchPeerType.Public ⇒ + val usersFull = searchGlobalUsers(text) + val usersPrefix = searchGlobalUsersPrefix(text) + val groupsPrefix = searchGlobalGroupsPrefix(text) for { - groups ← searchPublic(text) - } yield groups map (result(_, isPublic = true)) + uf ← usersFull + up ← usersPrefix + gp ← groupsPrefix + } yield (uf map result) ++ (up map result) ++ (gp map result) } } + // from contacts private def result(apiUser: ApiUser): ApiPeerSearchResult = ApiPeerSearchResult( peer = ApiPeer(ApiPeerType.Private, apiUser.id), - title = apiUser.localName.getOrElse(apiUser.name), - description = None, - membersCount = None, - dateCreated = None, - creator = None, - isPublic = None, - isJoined = None + optMatchString = None ) - private def result(apiGroup: ApiGroup, isPublic: Boolean): ApiPeerSearchResult = + // from local groups + private def result(apiGroup: ApiGroup): ApiPeerSearchResult = ApiPeerSearchResult( peer = ApiPeer(ApiPeerType.Group, apiGroup.id), - title = apiGroup.title, - description = apiGroup.about, - membersCount = Some(apiGroup.members.size), - dateCreated = Some(apiGroup.createDate), - creator = Some(apiGroup.creatorUserId), - isPublic = Some(isPublic), - isJoined = apiGroup.isMember + optMatchString = None + ) + + // from global peer + private def result(peer: ApiPeer): ApiPeerSearchResult = + ApiPeerSearchResult( + peer = peer, + optMatchString = None + ) + + // from global peer with nickname match + private def result(peerAndMatch: PeerAndMatchString): ApiPeerSearchResult = + ApiPeerSearchResult( + peer = peerAndMatch._1, + optMatchString = Some(peerAndMatch._2) ) + private def userPeersWithoutSelf(userIds: Seq[Int])(implicit client: AuthorizedClientData): Vector[ApiPeer] = + (userIds collect { + case userId if userId != client.userId ⇒ ApiPeer(ApiPeerType.Private, userId) + }).toVector + + // search users by full phone number, email or nickname + private def searchGlobalUsers(text: Option[String])(implicit client: AuthorizedClientData): Future[IndexedSeq[ApiPeer]] = { + text map { query ⇒ + userExt.findUserIds(query) map userPeersWithoutSelf + } getOrElse FastFuture.successful(Vector.empty) + } + + // search users by nickname prefix + private def searchGlobalUsersPrefix(text: Option[String])(implicit client: AuthorizedClientData): Future[IndexedSeq[PeerAndMatchString]] = { + text map { query ⇒ + globalNamesStorage.userIdsByPrefix(query) map { results ⇒ + results collect { + case (userId, nickName) if userId != client.userId ⇒ + ApiPeer(ApiPeerType.Private, userId) → nickName + } + } + } getOrElse FastFuture.successful(Vector.empty) + } + + // find groups by global name prefix + private def searchGlobalGroupsPrefix(text: Option[String]): Future[IndexedSeq[PeerAndMatchString]] = { + text map { query ⇒ + globalNamesStorage.groupIdsByPrefix(query) map { results ⇒ + results map { + case (groupId, globalName) ⇒ + ApiPeer(ApiPeerType.Group, groupId) → globalName + } + } + } getOrElse FastFuture.successful(Vector.empty) + } + private def searchContacts(text: Option[String])(implicit client: AuthorizedClientData): Future[IndexedSeq[ApiUser]] = { for { userIds ← db.run(UserContactRepo.findContactIdsActive(client.userId)) @@ -146,7 +205,7 @@ class SearchServiceImpl(implicit system: ActorSystem) extends SearchService { } // TODO: rewrite it using async - private def searchGroups(text: Option[String])(implicit client: AuthorizedClientData): Future[IndexedSeq[ApiGroup]] = { + private def searchLocalGroups(text: Option[String])(implicit client: AuthorizedClientData): Future[IndexedSeq[ApiGroup]] = { for { ids ← DialogExtension(system).fetchGroupedDialogs(client.userId) map (_.filter(_.typ.isGroups).flatMap(_.dialogs.map(_.getPeer.id))) groups ← FutureExt.ftraverse(ids) { id ⇒ @@ -155,13 +214,6 @@ class SearchServiceImpl(implicit system: ActorSystem) extends SearchService { } yield filterGroups(groups.toVector, text) } - private def searchPublic(text: Option[String])(implicit client: AuthorizedClientData): Future[IndexedSeq[ApiGroup]] = { - for { - groups ← db.run(GroupRepo.findPublic) // FIXME: isPublic flag is deprecated and will not appear for new groups - groups ← FutureExt.ftraverse(groups)(g ⇒ groupExt.getApiStruct(g.id, client.userId)) - } yield filterGroups(groups.toVector, text) - } - private def filterGroups(groups: IndexedSeq[ApiGroup], textOpt: Option[String]): IndexedSeq[ApiGroup] = { textOpt match { case Some(text) ⇒ From 1e613090df76af51cf14233d4d9794163ce8e23a Mon Sep 17 00:00:00 2001 From: rockjam Date: Thu, 21 Jul 2016 02:29:26 +0300 Subject: [PATCH 166/414] refactor(server): unify search results to ApiPeer --- .../service/search/SearchServiceImpl.scala | 42 +++++++------------ 1 file changed, 14 insertions(+), 28 deletions(-) diff --git a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/search/SearchServiceImpl.scala b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/search/SearchServiceImpl.scala index 1bff78bb62..c3647ec323 100644 --- a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/search/SearchServiceImpl.scala +++ b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/search/SearchServiceImpl.scala @@ -26,7 +26,7 @@ class SearchServiceImpl(implicit system: ActorSystem) extends SearchService { private val groupExt = GroupExtension(system) private val globalNamesStorage = new GlobalNamesStorageKeyValueStorage - private val EmptySearch = ResponsePeerSearch(Vector.empty, Vector.empty, Vector.empty, Vector.empty, Vector.empty) + private val EmptyResult = ResponsePeerSearch(Vector.empty, Vector.empty, Vector.empty, Vector.empty, Vector.empty) override def doHandlePeerSearch( query: IndexedSeq[ApiSearchCondition], @@ -42,7 +42,7 @@ class SearchServiceImpl(implicit system: ActorSystem) extends SearchService { texts.toList match { case text :: Nil if text.length < 3 ⇒ - FastFuture.successful(Ok(EmptySearch)) + FastFuture.successful(Ok(EmptyResult)) case text :: Nil ⇒ val tps = if (peerTypes.isEmpty) Set(ApiSearchPeerType.Public, ApiSearchPeerType.Contacts, ApiSearchPeerType.Groups) @@ -109,7 +109,6 @@ class SearchServiceImpl(implicit system: ActorSystem) extends SearchService { for { groups ← searchLocalGroups(text) } yield groups map result - // global search case ApiSearchPeerType.Public ⇒ val usersFull = searchGlobalUsers(text) val usersPrefix = searchGlobalUsersPrefix(text) @@ -122,28 +121,12 @@ class SearchServiceImpl(implicit system: ActorSystem) extends SearchService { } } - // from contacts - private def result(apiUser: ApiUser): ApiPeerSearchResult = - ApiPeerSearchResult( - peer = ApiPeer(ApiPeerType.Private, apiUser.id), - optMatchString = None - ) - - // from local groups - private def result(apiGroup: ApiGroup): ApiPeerSearchResult = - ApiPeerSearchResult( - peer = ApiPeer(ApiPeerType.Group, apiGroup.id), - optMatchString = None - ) - - // from global peer private def result(peer: ApiPeer): ApiPeerSearchResult = ApiPeerSearchResult( peer = peer, optMatchString = None ) - // from global peer with nickname match private def result(peerAndMatch: PeerAndMatchString): ApiPeerSearchResult = ApiPeerSearchResult( peer = peerAndMatch._1, @@ -186,26 +169,27 @@ class SearchServiceImpl(implicit system: ActorSystem) extends SearchService { } getOrElse FastFuture.successful(Vector.empty) } - private def searchContacts(text: Option[String])(implicit client: AuthorizedClientData): Future[IndexedSeq[ApiUser]] = { + private def searchContacts(text: Option[String])(implicit client: AuthorizedClientData): Future[IndexedSeq[ApiPeer]] = { for { userIds ← db.run(UserContactRepo.findContactIdsActive(client.userId)) users ← FutureExt.ftraverse(userIds)(userExt.getApiStruct(_, client.userId, client.authId)) } yield filterUsers(users.toVector, text) } - private def filterUsers(users: IndexedSeq[ApiUser], textOpt: Option[String]): IndexedSeq[ApiUser] = + private def filterUsers(users: IndexedSeq[ApiUser], textOpt: Option[String]): IndexedSeq[ApiPeer] = textOpt match { case Some(text) ⇒ val lotext = text.toLowerCase users filter { user ⇒ user.name.toLowerCase.contains(lotext) || user.localName.exists(_.toLowerCase.contains(lotext)) + } map { u ⇒ + ApiPeer(ApiPeerType.Private, u.id) } - case None ⇒ users + case None ⇒ users map { u ⇒ ApiPeer(ApiPeerType.Private, u.id) } } - // TODO: rewrite it using async - private def searchLocalGroups(text: Option[String])(implicit client: AuthorizedClientData): Future[IndexedSeq[ApiGroup]] = { + private def searchLocalGroups(text: Option[String])(implicit client: AuthorizedClientData): Future[IndexedSeq[ApiPeer]] = { for { ids ← DialogExtension(system).fetchGroupedDialogs(client.userId) map (_.filter(_.typ.isGroups).flatMap(_.dialogs.map(_.getPeer.id))) groups ← FutureExt.ftraverse(ids) { id ⇒ @@ -214,16 +198,18 @@ class SearchServiceImpl(implicit system: ActorSystem) extends SearchService { } yield filterGroups(groups.toVector, text) } - private def filterGroups(groups: IndexedSeq[ApiGroup], textOpt: Option[String]): IndexedSeq[ApiGroup] = { + private def filterGroups(groups: IndexedSeq[ApiGroup], textOpt: Option[String]): IndexedSeq[ApiPeer] = { textOpt match { case Some(text) ⇒ - groups.view.filter { group ⇒ + groups filter { group ⇒ val lotext = text.toLowerCase group.title.toLowerCase.contains(lotext) || group.about.exists(_.toLowerCase.contains(lotext)) || group.theme.exists(_.toLowerCase.contains(lotext)) - }.force - case None ⇒ groups + } map { g ⇒ + ApiPeer(ApiPeerType.Group, g.id) + } + case None ⇒ groups map { g ⇒ ApiPeer(ApiPeerType.Group, g.id) } } } } From 479eae5fd914c489a4eee39f75b2703851769444 Mon Sep 17 00:00:00 2001 From: rockjam Date: Thu, 21 Jul 2016 04:03:01 +0300 Subject: [PATCH 167/414] fix(server:groups): write dummy data to state of deleted group --- .../im/actor/server/group/GroupProcessor.scala | 2 +- .../scala/im/actor/server/group/GroupState.scala | 15 ++++++++++++++- .../rpc/service/messaging/HistoryHandlers.scala | 1 + 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala index faf98a19a5..fd54c7dfc8 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala @@ -169,7 +169,7 @@ private[group] final class GroupProcessor protected def handleQuery: PartialFunction[Any, Future[Any]] = { case _: GroupQuery if state.isNotCreated ⇒ FastFuture.failed(GroupNotFound(groupId)) - case _: GroupQuery if state.isDeleted ⇒ FastFuture.failed(GroupAlreadyDeleted(groupId)) + // case _: GroupQuery if state.isDeleted ⇒ FastFuture.failed(GroupAlreadyDeleted(groupId)) // TODO: figure out how to propperly handle group deletion case GetAccessHash() ⇒ getAccessHash case GetTitle() ⇒ getTitle case GetIntegrationToken(optClient) ⇒ getIntegrationToken(optClient) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala index c289fdd382..7ee9b0da92 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala @@ -268,8 +268,21 @@ private[group] final case class GroupState( case HistoryBecameShared(_, _) ⇒ this.copy(isHistoryShared = true) case GroupDeleted(ts, _) ⇒ - this.copy(deletedAt = Some(ts)) + // FIXME: don't implement snapshots, before figure out deleted groups behavior + this.copy( + deletedAt = Some(ts), + members = Map.empty, + invitedUserIds = Set.empty, + exUserIds = Set.empty, + bot = None, + topic = None, + about = None, + avatar = None, + adminSettings = + if (groupType.isChannel) AdminSettings.ChannelsDefault + else AdminSettings.PlainDefault + ) // deprecated events case UserBecameAdmin(_, userId, _) ⇒ this.copy( diff --git a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/messaging/HistoryHandlers.scala b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/messaging/HistoryHandlers.scala index 1735f482cd..4223d3ed4d 100644 --- a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/messaging/HistoryHandlers.scala +++ b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/messaging/HistoryHandlers.scala @@ -140,6 +140,7 @@ trait HistoryHandlers { case ApiPeerType.Private ⇒ (uids :+ dialog.peer.id, gids) } } + // TODO: make like here: im.actor.server.api.rpc.service.groups.GroupsServiceImpl.usersOrPeers (groups, users) ← GroupUtils.getGroupsUsers(groupIds, userIds, client.userId, client.authId) archivedExist ← dialogExt.fetchArchivedDialogs(client.userId, None, 1) map (_._1.nonEmpty) showInvite ← db.run(UserContactRepo.count(client.userId)) map (_ < 5) From 5d18e2e35cada6b01ecd0ffddffd8e7f3922fb23 Mon Sep 17 00:00:00 2001 From: rockjam Date: Thu, 21 Jul 2016 04:04:10 +0300 Subject: [PATCH 168/414] fix(server:test): compilation error --- .../im/actor/server/api/rpc/service/SearchServiceSpec.scala | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/actor-server/actor-tests/src/test/scala/im/actor/server/api/rpc/service/SearchServiceSpec.scala b/actor-server/actor-tests/src/test/scala/im/actor/server/api/rpc/service/SearchServiceSpec.scala index e4956cd936..83249eab0b 100644 --- a/actor-server/actor-tests/src/test/scala/im/actor/server/api/rpc/service/SearchServiceSpec.scala +++ b/actor-server/actor-tests/src/test/scala/im/actor/server/api/rpc/service/SearchServiceSpec.scala @@ -40,9 +40,6 @@ final class SearchServiceSpec groups shouldBe empty users.map(_.id) shouldBe Seq(user2.id) results.length shouldBe 1 - val result = results.head - - result.title shouldBe user2.name } } } @@ -84,4 +81,4 @@ final class SearchServiceSpec } } } -} \ No newline at end of file +} From 21ed83b4074b7ca46f9fb8861da6f42d6acee2bd Mon Sep 17 00:00:00 2001 From: rockjam Date: Thu, 21 Jul 2016 04:35:49 +0300 Subject: [PATCH 169/414] fix(server:dialogs): load last message for peers with shared history --- .../actor/server/dialog/DialogExtension.scala | 17 +++++++++-------- .../server/persist/HistoryMessageRepo.scala | 14 ++------------ 2 files changed, 11 insertions(+), 20 deletions(-) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogExtension.scala b/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogExtension.scala index 69a2611377..be400eeb5f 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogExtension.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogExtension.scala @@ -378,21 +378,22 @@ final class DialogExtensionImpl(system: ActorSystem) extends DialogExtension wit .map(_.info.get) } + private val EmptyTextMessage = ApiTextMessage(text = "", mentions = Vector.empty, ext = None) + def getApiDialog(userId: Int, info: DialogInfo, sortDate: Instant): Future[ApiDialog] = { - val emptyMessageContent = ApiTextMessage(text = "", mentions = Vector.empty, ext = None) - val emptyMessage = HistoryMessage(userId, info.peer.get, new DateTime(0), 0, 0, emptyMessageContent.header, emptyMessageContent.toByteArray, None) + val emptyMessage = HistoryMessage(userId, info.peer.get, new DateTime(0), 0, 0, EmptyTextMessage.header, EmptyTextMessage.toByteArray, None) val peer = info.peer.get for { historyOwner ← getHistoryOwner(peer, userId) - messageOpt ← getLastMessage(userId, peer) - reactions ← messageOpt map (m ⇒ db.run(fetchReactions(peer, userId, m.randomId))) getOrElse FastFuture.successful(Vector.empty) - message ← getLastMessage(userId, peer) map (_ getOrElse emptyMessage) map (_.asStruct( + lastMessageOpt ← getLastMessage(historyOwner, peer) + reactions ← lastMessageOpt map (m ⇒ db.run(fetchReactions(peer, userId, m.randomId))) getOrElse FastFuture.successful(Vector.empty) + message = lastMessageOpt.getOrElse(emptyMessage).asStruct( lastReceivedAt = new DateTime(info.lastReceivedDate.toEpochMilli), lastReadAt = new DateTime(info.lastReadDate.toEpochMilli), reactions = reactions, attributes = None - )) map (_.getOrElse(throw new RuntimeException("Failed to get message struct"))) + ) getOrElse (throw new RuntimeException("Failed to get message struct")) firstUnreadOpt ← db.run(HistoryMessageRepo.findAfter(historyOwner, peer, new DateTime(info.lastReadDate.toEpochMilli), 1) map (_.headOption map (_.ofUser(userId)))) } yield ApiDialog( peer = peer.asStruct, @@ -423,8 +424,8 @@ final class DialogExtensionImpl(system: ActorSystem) extends DialogExtension wit .withDialogEnvelope(DialogEnvelope().withSendMessage(sendMessage))).mapTo[SeqStateDate] } - private def getLastMessage(userId: Int, peer: Peer): Future[Option[HistoryMessage]] = - db.run(HistoryMessageRepo.findNewest(userId, peer)) + private def getLastMessage(historyOwner: Int, peer: Peer): Future[Option[HistoryMessage]] = + db.run(HistoryMessageRepo.findNewest(historyOwner, peer)) private def reactions(events: Seq[ReactionEvent]): Seq[MessageReaction] = { (events.view groupBy (_.code) mapValues (_ map (_.userId)) map { diff --git a/actor-server/actor-persist/src/main/scala/im/actor/server/persist/HistoryMessageRepo.scala b/actor-server/actor-persist/src/main/scala/im/actor/server/persist/HistoryMessageRepo.scala index 4ce5087c68..fc80a2712b 100644 --- a/actor-server/actor-persist/src/main/scala/im/actor/server/persist/HistoryMessageRepo.scala +++ b/actor-server/actor-persist/src/main/scala/im/actor/server/persist/HistoryMessageRepo.scala @@ -149,23 +149,13 @@ object HistoryMessageRepo { .map(_.userId) .result - def findNewest(userId: Int, peer: Peer): SqlAction[Option[HistoryMessage], NoStream, Read] = { - val filter = { m: HistoryMessageTable ⇒ - m.userId === userId && - m.peerType === peer.typ.value && - m.peerId === peer.id - } - findNewestFilter(userId, peer, filter) - } - - private def findNewestFilter(userId: Int, peer: Peer, filterClause: HistoryMessageTable ⇒ Rep[Boolean]) = { + def findNewest(userId: Int, peer: Peer): SqlAction[Option[HistoryMessage], NoStream, Read] = notDeletedMessages - .filter(filterClause) + .filter(m ⇒ m.userId === userId && m.peerType === peer.typ.value && m.peerId === peer.id) .sortBy(_.date.desc) .take(1) .result .headOption - } def find(userId: Int, peer: Peer): FixedSqlStreamingAction[Seq[HistoryMessage], HistoryMessage, Read] = notDeletedMessages From 8904bec2b8ed6dbf752ef37215006007aca1f222 Mon Sep 17 00:00:00 2001 From: rockjam Date: Thu, 21 Jul 2016 05:47:02 +0300 Subject: [PATCH 170/414] fix(server): compilation error --- .../server/api/rpc/service/search/SearchServiceImpl.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/search/SearchServiceImpl.scala b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/search/SearchServiceImpl.scala index c3647ec323..88064e02ac 100644 --- a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/search/SearchServiceImpl.scala +++ b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/search/SearchServiceImpl.scala @@ -21,7 +21,8 @@ import scala.concurrent.{ ExecutionContext, Future } class SearchServiceImpl(implicit system: ActorSystem) extends SearchService { override implicit protected val ec: ExecutionContext = system.dispatcher - private val db = DbExtension(system).db + protected val db = DbExtension(system).db + private val userExt = UserExtension(system) private val groupExt = GroupExtension(system) private val globalNamesStorage = new GlobalNamesStorageKeyValueStorage From 8265037c1798ea49b3dcb0e6b15c4fd85907b136 Mon Sep 17 00:00:00 2001 From: rockjam Date: Thu, 21 Jul 2016 06:59:47 +0300 Subject: [PATCH 171/414] fix(server:sequence): rewrite difference to avoid counters madness --- .../actor/server/dialog/DialogExtension.scala | 24 ++++-- .../im/actor/server/dialog/DialogRoot.scala | 12 ++- .../operations/DeliveryOperations.scala | 5 +- .../operations/DifferenceOperations.scala | 83 ++++++++++++++++++- .../scala/im/actor/server/persist/Group.scala | 3 - .../sequence/SequenceServiceImpl.scala | 1 + 6 files changed, 111 insertions(+), 17 deletions(-) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogExtension.scala b/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogExtension.scala index be400eeb5f..3b7d1c5220 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogExtension.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogExtension.scala @@ -463,22 +463,34 @@ final class DialogExtensionImpl(system: ActorSystem) extends DialogExtension wit } } +object DialogGroupKeys { + val Favourites = "favourites" + val Direct = "privates" + val Groups = "groups" +} + +object DialogGroupTitles { + val Favourites = "Favourites" + val Direct = "Direct Messages" + val Groups = "Groups" +} + object DialogExtension extends ExtensionId[DialogExtensionImpl] with ExtensionIdProvider { override def lookup = DialogExtension override def createExtension(system: ExtendedActorSystem) = new DialogExtensionImpl(system) def groupKey(group: DialogGroupType) = group match { - case DialogGroupType.Favourites ⇒ "favourites" - case DialogGroupType.DirectMessages ⇒ "privates" - case DialogGroupType.Groups ⇒ "groups" + case DialogGroupType.Favourites ⇒ DialogGroupKeys.Favourites + case DialogGroupType.DirectMessages ⇒ DialogGroupKeys.Direct + case DialogGroupType.Groups ⇒ DialogGroupKeys.Groups case unknown ⇒ throw DialogErrors.UnknownDialogGroupType(unknown) } def groupTitle(group: DialogGroupType) = group match { - case DialogGroupType.Favourites ⇒ "Favourites" - case DialogGroupType.DirectMessages ⇒ "Direct Messages" - case DialogGroupType.Groups ⇒ "Groups" + case DialogGroupType.Favourites ⇒ DialogGroupTitles.Favourites + case DialogGroupType.DirectMessages ⇒ DialogGroupTitles.Direct + case DialogGroupType.Groups ⇒ DialogGroupTitles.Groups case unknown ⇒ throw DialogErrors.UnknownDialogGroupType(unknown) } } diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogRoot.scala b/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogRoot.scala index e65030de63..e308b0f305 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogRoot.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogRoot.scala @@ -107,6 +107,7 @@ private class DialogRoot(val userId: Int, extensions: Seq[ApiExtension]) import context.dispatcher private val system = context.system + private val seqUpdExt = SeqUpdatesExtension(system) private val userExt = UserExtension(system) private val groupExt = GroupExtension(system) private val db = DbExtension(system).db @@ -218,7 +219,7 @@ private class DialogRoot(val userId: Int, extensions: Seq[ApiExtension]) commit(e) (for { _ ← db.run(HistoryMessageRepo.deleteAll(userId, peer)) - _ ← SeqUpdatesExtension(system).deliverUserUpdate(userId, UpdateChatDelete(peer.asStruct)) + _ ← seqUpdExt.deliverUserUpdate(userId, UpdateChatDelete(peer.asStruct)) seqState ← sendChatGroupsChanged(clientAuthId) // _ = thatDialog ! PoisonPill // kill that dialog would be good } yield seqState) pipeTo sender() @@ -273,8 +274,13 @@ private class DialogRoot(val userId: Int, extensions: Seq[ApiExtension]) for { groups ← DialogExtension(system).fetchApiGroupedDialogs(userId) update = UpdateChatGroupsChanged(groups) - seqState ← SeqUpdatesExtension(system) - .deliverClientUpdate(userId, authId, update, pushRules) + seqState ← seqUpdExt.deliverClientUpdate( + userId, + authId, + update, + pushRules, + deliveryId = s"dialogschanged_${userId}" + ) } yield seqState } diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/sequence/operations/DeliveryOperations.scala b/actor-server/actor-core/src/main/scala/im/actor/server/sequence/operations/DeliveryOperations.scala index 38c7c2140e..04204cd210 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/sequence/operations/DeliveryOperations.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/sequence/operations/DeliveryOperations.scala @@ -1,9 +1,8 @@ package im.actor.server.sequence.operations import com.google.protobuf.ByteString -import com.google.protobuf.wrappers.StringValue import im.actor.api.rpc.Update -import im.actor.server.model.{ Peer, SerializedUpdate, UpdateMapping } +import im.actor.server.model.{ SerializedUpdate, UpdateMapping } import im.actor.server.sequence.UserSequenceCommands.{ DeliverUpdate, Envelope } import im.actor.server.sequence.{ PushData, PushRules, SeqState, SeqUpdatesExtension } import akka.pattern.ask @@ -154,7 +153,7 @@ trait DeliveryOperations { this: SeqUpdatesExtension ⇒ (region.ref ? Envelope(userId).withDeliverUpdate(deliver)).mapTo[SeqState] } - private def serializedUpdate(u: Update): SerializedUpdate = + protected def serializedUpdate(u: Update): SerializedUpdate = SerializedUpdate(u.header, ByteString.copyFrom(u.toByteArray), u._relatedUserIds, u._relatedGroupIds) private def buildDeliver( diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/sequence/operations/DifferenceOperations.scala b/actor-server/actor-core/src/main/scala/im/actor/server/sequence/operations/DifferenceOperations.scala index 430ab302e5..51239eacd1 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/sequence/operations/DifferenceOperations.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/sequence/operations/DifferenceOperations.scala @@ -1,7 +1,10 @@ package im.actor.server.sequence.operations import akka.http.scaladsl.util.FastFuture +import im.actor.api.rpc.messaging.{ ApiDialogGroup, ApiDialogShort, UpdateChatGroupsChanged, UpdateMessageReadByMe } +import im.actor.api.rpc.peers.ApiPeer import im.actor.api.rpc.sequence.UpdateEmptyUpdate +import im.actor.server.dialog.{ DialogGroupKeys, DialogGroupTitles } import im.actor.server.model.{ SeqUpdate, SerializedUpdate, UpdateMapping } import im.actor.server.persist.sequence.UserSequenceRepo import im.actor.server.sequence.{ CommonState, Difference, SeqUpdatesExtension } @@ -18,7 +21,12 @@ trait DifferenceOperations { this: SeqUpdatesExtension ⇒ private type ReduceKey = String private object DiffAcc { - def empty(commonSeq: Int) = DiffAcc(commonSeq, 0, immutable.TreeMap.empty, Map.empty) + def empty(commonSeq: Int) = DiffAcc( + commonSeq = commonSeq, + seqDelta = 0, + generic = immutable.TreeMap.empty, + reduced = Map.empty + ) } /** @@ -42,7 +50,78 @@ trait DifferenceOperations { this: SeqUpdatesExtension ⇒ ) { def nonEmpty = generic.nonEmpty || reduced.nonEmpty - def toVector = (generic ++ reduced.values).values.toVector + private def rewriteDialogsCounter(dialogs: IndexedSeq[ApiDialogShort], upd: UpdateMessageReadByMe) = + dialogs map { dlg ⇒ + if (upd.peer == dlg.peer) { + dlg.copy(counter = upd.unreadCounter getOrElse 0) + } else { + dlg + } + } + + def toVector = { + val originalUpdates = (generic ++ reduced.values).values.toVector + + // If there is UpdateChatGroupsChanged in difference, + // we are writing up to date counters from UpdateMessageReadByMe + // for each peer we have in UpdateMessageReadByMe updates. + // After that, we update original difference + val optLastChatsChanged: Option[(Int, UpdateChatGroupsChanged)] = + originalUpdates.zipWithIndex.reverse find { + case (upd, i) ⇒ + upd.header == UpdateChatGroupsChanged.header + } flatMap { + case (upd, i) ⇒ + UpdateChatGroupsChanged.parseFrom(upd.body).right.toOption map (i → _) + } + + optLastChatsChanged match { + case None ⇒ originalUpdates + case Some((index, chatsChanged)) ⇒ + def singleGroup(groupKey: String) = (chatsChanged.dialogs collect { + case group if group.key == groupKey ⇒ group.dialogs + }).flatten + + val (groups, direct, favourites) = (originalUpdates foldLeft ( + singleGroup(DialogGroupKeys.Groups), + singleGroup(DialogGroupKeys.Direct), + singleGroup(DialogGroupKeys.Favourites) + )) { + case (acc @ (gr, dir, fav), upd) ⇒ + if (upd.header == UpdateMessageReadByMe.header) { + UpdateMessageReadByMe.parseFrom(upd.body).right.toOption map { upd ⇒ + ( + rewriteDialogsCounter(gr, upd), + rewriteDialogsCounter(dir, upd), + rewriteDialogsCounter(fav, upd) + ) + } getOrElse acc + } else acc + } + + val chatsChangedUpdated = + chatsChanged.copy( + Vector( + ApiDialogGroup( + title = DialogGroupTitles.Groups, + key = DialogGroupKeys.Groups, + dialogs = groups + ), + ApiDialogGroup( + title = DialogGroupTitles.Direct, + key = DialogGroupKeys.Direct, + dialogs = direct + ), + ApiDialogGroup( + title = DialogGroupTitles.Favourites, + key = DialogGroupKeys.Favourites, + dialogs = favourites + ) + ) + ) + originalUpdates.updated(index, serializedUpdate(chatsChangedUpdated)) + } + } } def getDifference( diff --git a/actor-server/actor-persist/src/main/scala/im/actor/server/persist/Group.scala b/actor-server/actor-persist/src/main/scala/im/actor/server/persist/Group.scala index e5aeda7470..86bb7cc3e0 100644 --- a/actor-server/actor-persist/src/main/scala/im/actor/server/persist/Group.scala +++ b/actor-server/actor-persist/src/main/scala/im/actor/server/persist/Group.scala @@ -93,9 +93,6 @@ object GroupRepo { ) } - def findPublic = - groups.filter(_.isPublic === true).map(_.asGroup).result - @deprecated("Replace with some sort of key-value maybe?", "2016-06-05") def findAllIds = allIds.result diff --git a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/sequence/SequenceServiceImpl.scala b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/sequence/SequenceServiceImpl.scala index 4c15592915..478e5256cb 100644 --- a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/sequence/SequenceServiceImpl.scala +++ b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/sequence/SequenceServiceImpl.scala @@ -161,6 +161,7 @@ final class SequenceServiceImpl(config: SequenceServiceConfig)( } } + // TODO: add non deleted check too. private def getNonChannelsIds(groups: Seq[ApiGroupOutPeer]): Future[Seq[Int]] = { FutureExt.ftraverse(groups) { case ApiGroupOutPeer(groupId, _) ⇒ From 056815769059e84344e1a0432d46835b9f71705d Mon Sep 17 00:00:00 2001 From: rockjam Date: Thu, 21 Jul 2016 07:59:24 +0300 Subject: [PATCH 172/414] fix(server:dialogs): reduce key for update instead of delivery id, reorder dialogs list --- .../im/actor/server/dialog/DialogRoot.scala | 2 +- .../operations/DifferenceOperations.scala | 22 +++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogRoot.scala b/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogRoot.scala index e308b0f305..eaae17e4e8 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogRoot.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogRoot.scala @@ -279,7 +279,7 @@ private class DialogRoot(val userId: Int, extensions: Seq[ApiExtension]) authId, update, pushRules, - deliveryId = s"dialogschanged_${userId}" + reduceKey = Some(s"dialogschanged_${userId}") ) } yield seqState } diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/sequence/operations/DifferenceOperations.scala b/actor-server/actor-core/src/main/scala/im/actor/server/sequence/operations/DifferenceOperations.scala index 51239eacd1..b1a843ee10 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/sequence/operations/DifferenceOperations.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/sequence/operations/DifferenceOperations.scala @@ -82,18 +82,18 @@ trait DifferenceOperations { this: SeqUpdatesExtension ⇒ case group if group.key == groupKey ⇒ group.dialogs }).flatten - val (groups, direct, favourites) = (originalUpdates foldLeft ( + val (favourites, groups, direct) = (originalUpdates foldLeft ( + singleGroup(DialogGroupKeys.Favourites), singleGroup(DialogGroupKeys.Groups), - singleGroup(DialogGroupKeys.Direct), - singleGroup(DialogGroupKeys.Favourites) + singleGroup(DialogGroupKeys.Direct) )) { - case (acc @ (gr, dir, fav), upd) ⇒ + case (acc @ (fav, gr, dir), upd) ⇒ if (upd.header == UpdateMessageReadByMe.header) { UpdateMessageReadByMe.parseFrom(upd.body).right.toOption map { upd ⇒ ( + rewriteDialogsCounter(fav, upd), rewriteDialogsCounter(gr, upd), - rewriteDialogsCounter(dir, upd), - rewriteDialogsCounter(fav, upd) + rewriteDialogsCounter(dir, upd) ) } getOrElse acc } else acc @@ -102,6 +102,11 @@ trait DifferenceOperations { this: SeqUpdatesExtension ⇒ val chatsChangedUpdated = chatsChanged.copy( Vector( + ApiDialogGroup( + title = DialogGroupTitles.Favourites, + key = DialogGroupKeys.Favourites, + dialogs = favourites + ), ApiDialogGroup( title = DialogGroupTitles.Groups, key = DialogGroupKeys.Groups, @@ -111,11 +116,6 @@ trait DifferenceOperations { this: SeqUpdatesExtension ⇒ title = DialogGroupTitles.Direct, key = DialogGroupKeys.Direct, dialogs = direct - ), - ApiDialogGroup( - title = DialogGroupTitles.Favourites, - key = DialogGroupKeys.Favourites, - dialogs = favourites ) ) ) From e4b105e8c27af5ca86753ca2c8c41e380b72bd5c Mon Sep 17 00:00:00 2001 From: rockjam Date: Thu, 21 Jul 2016 15:17:59 +0300 Subject: [PATCH 173/414] chore(server): update actor.json --- .../actor-core/src/main/actor-api/actor.json | 541 ++---------------- 1 file changed, 56 insertions(+), 485 deletions(-) diff --git a/actor-server/actor-core/src/main/actor-api/actor.json b/actor-server/actor-core/src/main/actor-api/actor.json index 30da5aee54..d707e0a5ed 100644 --- a/actor-server/actor-core/src/main/actor-api/actor.json +++ b/actor-server/actor-core/src/main/actor-api/actor.json @@ -7882,6 +7882,20 @@ "name": "Group", "doc": [ "Group information", + "", + "Permissions.", + "Permissions of this structure is about group messages operation, such as", + "ability to send messages, clear chat, leave group and so on. This operations", + "Can be held outside of the Group Info page.", + "", + "Default value is ZERO, Opposide iz ONE. If Default is FALSE then ONE == TRUE.", + "If default is TRUE then ONE == FALSE.", + "Bits:", + "0 - canSendMessage. Default is FALSE.", + "1 - canClear. Default is FALSE.", + "2 - canLeave. Default is FALSE.", + "3 - canDelete. Default is FALSE.", + "", { "type": "reference", "argument": "id", @@ -7932,9 +7946,9 @@ }, { "type": "reference", - "argument": "canSendMessage", + "argument": "permissions", "category": "full", - "description": " Can user send messages. Default is equals isMember for Group and false for others." + "description": " Permissions of group object" }, { "type": "reference", @@ -8054,10 +8068,10 @@ { "type": { "type": "opt", - "childType": "bool" + "childType": "int64" }, "id": 26, - "name": "canSendMessage" + "name": "permissions" }, { "type": { @@ -8138,6 +8152,22 @@ "name": "GroupFull", "doc": [ "Goup Full information", + "Permissions.", + "Idea of Group Full mermissions is about Group Info pages. This permissions", + "are usefull only when trying to view and update group settings and not related", + "to chat messages itself.", + "Default value is ZERO, Opposide iz ONE. If Default is FALSE then ONE == TRUE.", + "If default is TRUE then ONE == FALSE.", + "Bits:", + "0 - canEditInfo. Default is FALSE.", + "1 - canViewMembers. Default is FALSE.", + "2 - canInviteMembers. Default is FALSE.", + "3 - canInviteViaLink. Default is FALSE.", + "4 - canCall. Default is FALSE.", + "5 - canEditAdminSettings. Default is FALSE.", + "6 - canViewAdmins. Default is FALSE.", + "7 - canEditAdmins. Default is FALSE.", + "", { "type": "reference", "argument": "id", @@ -8180,30 +8210,12 @@ "category": "full", "description": " Is Members need to be loaded asynchronous. Default is false." }, - { - "type": "reference", - "argument": "canViewMembers", - "category": "full", - "description": " Can current user view members of the group. Default is true." - }, - { - "type": "reference", - "argument": "canInvitePeople", - "category": "full", - "description": " Can current user invite new people. Default is true." - }, { "type": "reference", "argument": "isSharedHistory", "category": "full", "description": " Is history shared among all users. Default is false." }, - { - "type": "reference", - "argument": "canEditGroupInfo", - "category": "full", - "description": " If current user can edit group info. Default is true." - }, { "type": "reference", "argument": "shortName", @@ -8212,45 +8224,9 @@ }, { "type": "reference", - "argument": "canEditShortName", - "category": "full", - "description": " If not set only owner can edit group's short name" - }, - { - "type": "reference", - "argument": "canEditAdminList", - "category": "full", - "description": " If not set only owner can edit admin list" - }, - { - "type": "reference", - "argument": "canViewAdminList", - "category": "full", - "description": " If not set only owner and admins can view admin list" - }, - { - "type": "reference", - "argument": "canEditAdminSettings", + "argument": "permissions", "category": "full", - "description": " If not set only owner can edit admin settings" - }, - { - "type": "reference", - "argument": "canInviteViaLink", - "category": "full", - "description": " If user can invite via link, default is false" - }, - { - "type": "reference", - "argument": "canDelete", - "category": "full", - "description": " If user can delete this group, default is false" - }, - { - "type": "reference", - "argument": "canLeave", - "category": "hidden", - "description": " If user can leave this group, default is true" + "description": " Group Permissions" } ], "expandable": "true", @@ -8328,22 +8304,6 @@ "id": 11, "name": "isAsyncMembers" }, - { - "type": { - "type": "opt", - "childType": "bool" - }, - "id": 8, - "name": "canViewMembers" - }, - { - "type": { - "type": "opt", - "childType": "bool" - }, - "id": 9, - "name": "canInvitePeople" - }, { "type": { "type": "opt", @@ -8352,14 +8312,6 @@ "id": 10, "name": "isSharedHistory" }, - { - "type": { - "type": "opt", - "childType": "bool" - }, - "id": 13, - "name": "canEditGroupInfo" - }, { "type": { "type": "opt", @@ -8371,58 +8323,10 @@ { "type": { "type": "opt", - "childType": "bool" - }, - "id": 15, - "name": "canEditShortName" - }, - { - "type": { - "type": "opt", - "childType": "bool" - }, - "id": 16, - "name": "canEditAdminList" - }, - { - "type": { - "type": "opt", - "childType": "bool" - }, - "id": 17, - "name": "canViewAdminList" - }, - { - "type": { - "type": "opt", - "childType": "bool" - }, - "id": 18, - "name": "canEditAdminSettings" - }, - { - "type": { - "type": "opt", - "childType": "bool" - }, - "id": 19, - "name": "canInviteViaLink" - }, - { - "type": { - "type": "opt", - "childType": "bool" - }, - "id": 20, - "name": "canDelete" - }, - { - "type": { - "type": "opt", - "childType": "bool" + "childType": "int64" }, - "id": 21, - "name": "canLeave" + "id": 27, + "name": "permissions" } ] } @@ -8930,317 +8834,15 @@ { "type": "update", "content": { - "name": "GroupCanSendMessagesChanged", - "header": 2624, - "doc": [ - "Update about can send messages changed", - { - "type": "reference", - "argument": "groupId", - "category": "full", - "description": " Group Id" - }, - { - "type": "reference", - "argument": "canSendMessages", - "category": "full", - "description": " Can send messages" - } - ], - "attributes": [ - { - "type": { - "type": "alias", - "childType": "groupId" - }, - "id": 1, - "name": "groupId" - }, - { - "type": "bool", - "id": 2, - "name": "canSendMessages" - } - ] - } - }, - { - "type": "update", - "content": { - "name": "GroupCanViewMembersChanged", - "header": 2625, - "doc": [ - "Update about can view members changed", - { - "type": "reference", - "argument": "groupId", - "category": "full", - "description": " Group Id" - }, - { - "type": "reference", - "argument": "canViewMembers", - "category": "full", - "description": " Can view members" - } - ], - "attributes": [ - { - "type": { - "type": "alias", - "childType": "groupId" - }, - "id": 1, - "name": "groupId" - }, - { - "type": "bool", - "id": 2, - "name": "canViewMembers" - } - ] - } - }, - { - "type": "update", - "content": { - "name": "GroupCanInviteMembersChanged", - "header": 2626, - "doc": [ - "Update about can invite members changed", - { - "type": "reference", - "argument": "groupId", - "category": "full", - "description": " Group Id" - }, - { - "type": "reference", - "argument": "canInviteMembers", - "category": "full", - "description": " Can invite members" - } - ], - "attributes": [ - { - "type": { - "type": "alias", - "childType": "groupId" - }, - "id": 1, - "name": "groupId" - }, - { - "type": "bool", - "id": 2, - "name": "canInviteMembers" - } - ] - } - }, - { - "type": "update", - "content": { - "name": "GroupCanEditInfoChanged", - "header": 2631, - "doc": [ - "Update about can edit changed", - { - "type": "reference", - "argument": "groupId", - "category": "full", - "description": " Group Id" - }, - { - "type": "reference", - "argument": "canEditGroup", - "category": "full", - "description": " Can edit group info" - } - ], - "attributes": [ - { - "type": { - "type": "alias", - "childType": "groupId" - }, - "id": 1, - "name": "groupId" - }, - { - "type": "bool", - "id": 2, - "name": "canEditGroup" - } - ] - } - }, - { - "type": "update", - "content": { - "name": "GroupCanEditUsernameChanged", - "header": 2632, - "doc": [ - "Update about can edit username changed", - { - "type": "reference", - "argument": "groupId", - "category": "full", - "description": " Group Id" - }, - { - "type": "reference", - "argument": "canEditUsername", - "category": "full", - "description": " Can edit username" - } - ], - "attributes": [ - { - "type": { - "type": "alias", - "childType": "groupId" - }, - "id": 1, - "name": "groupId" - }, - { - "type": "bool", - "id": 2, - "name": "canEditUsername" - } - ] - } - }, - { - "type": "update", - "content": { - "name": "GroupCanEditAdminsChanged", - "header": 2633, - "doc": [ - "Update about can edit admins changed", - { - "type": "reference", - "argument": "groupId", - "category": "full", - "description": " Group Id" - }, - { - "type": "reference", - "argument": "canAssignAdmins", - "category": "hidden", - "description": " Can assign admins" - } - ], - "attributes": [ - { - "type": { - "type": "alias", - "childType": "groupId" - }, - "id": 1, - "name": "groupId" - }, - { - "type": "bool", - "id": 2, - "name": "canAssignAdmins" - } - ] - } - }, - { - "type": "update", - "content": { - "name": "GroupCanViewAdminsChanged", - "header": 2640, - "doc": [ - "Update about view admings changed", - { - "type": "reference", - "argument": "groupId", - "category": "full", - "description": " Group Id" - }, - { - "type": "reference", - "argument": "canViewAdmins", - "category": "hidden", - "description": " Can view admins" - } - ], - "attributes": [ - { - "type": { - "type": "alias", - "childType": "groupId" - }, - "id": 1, - "name": "groupId" - }, - { - "type": "bool", - "id": 2, - "name": "canViewAdmins" - } - ] - } - }, - { - "type": "update", - "content": { - "name": "GroupCanInviteViaLink", - "header": 2646, - "doc": [ - "Update about can invite via link changed", - { - "type": "reference", - "argument": "groupId", - "category": "full", - "description": " Group Id" - }, - { - "type": "reference", - "argument": "canInviteViaLink", - "category": "full", - "description": " Can Invite Via link" - } - ], - "attributes": [ - { - "type": { - "type": "alias", - "childType": "groupId" - }, - "id": 1, - "name": "groupId" - }, - { - "type": "bool", - "id": 2, - "name": "canInviteViaLink" - } - ] - } - }, - { - "type": "update", - "content": { - "name": "GroupCanLeaveChanged", - "header": 2647, + "name": "GroupDeleted", + "header": 2658, "doc": [ - "Update about can leave changed", + "Update about group deleted", { "type": "reference", "argument": "groupId", "category": "full", "description": " Group Id" - }, - { - "type": "reference", - "argument": "canLeaveChanged", - "category": "full", - "description": " Can leave changed" } ], "attributes": [ @@ -9251,11 +8853,6 @@ }, "id": 1, "name": "groupId" - }, - { - "type": "bool", - "id": 2, - "name": "canLeaveChanged" } ] } @@ -9263,10 +8860,10 @@ { "type": "update", "content": { - "name": "GroupCanDeleteChanged", - "header": 2648, + "name": "GroupPermissionsChanged", + "header": 2663, "doc": [ - "Update about can delete changed", + "Update about group permissions changed", { "type": "reference", "argument": "groupId", @@ -9275,9 +8872,9 @@ }, { "type": "reference", - "argument": "canDeleteChanged", + "argument": "permissions", "category": "full", - "description": " Can delete changed" + "description": " New Permissions" } ], "attributes": [ @@ -9290,9 +8887,9 @@ "name": "groupId" }, { - "type": "bool", + "type": "int64", "id": 2, - "name": "canDeleteChanged" + "name": "permissions" } ] } @@ -9300,10 +8897,10 @@ { "type": "update", "content": { - "name": "GroupCanEditAdminSettingsChanged", - "header": 2641, + "name": "GroupFullPermissionsChanged", + "header": 2664, "doc": [ - "Update about edit admin settings changed", + "Update about Full Group permissions changed", { "type": "reference", "argument": "groupId", @@ -9312,9 +8909,9 @@ }, { "type": "reference", - "argument": "canEditAdminSettings", + "argument": "permissions", "category": "full", - "description": " Can edit admin settings" + "description": " New Permissions" } ], "attributes": [ @@ -9327,35 +8924,9 @@ "name": "groupId" }, { - "type": "bool", + "type": "int64", "id": 2, - "name": "canEditAdminSettings" - } - ] - } - }, - { - "type": "update", - "content": { - "name": "GroupDeleted", - "header": 2658, - "doc": [ - "Update about group deleted", - { - "type": "reference", - "argument": "groupId", - "category": "full", - "description": " Group Id" - } - ], - "attributes": [ - { - "type": { - "type": "alias", - "childType": "groupId" - }, - "id": 1, - "name": "groupId" + "name": "permissions" } ] } From b37072349a80e8c72809acccbb792eb8b32f2cf2 Mon Sep 17 00:00:00 2001 From: rockjam Date: Thu, 21 Jul 2016 19:34:32 +0300 Subject: [PATCH 174/414] feat(server:groups): bitmask based permission handling --- .../server/group/AdminCommandHandlers.scala | 127 ++++++---------- .../server/group/GroupCommandHandlers.scala | 7 + .../actor/server/group/GroupProcessor.scala | 19 ++- .../server/group/GroupQueryHandlers.scala | 16 +-- .../im/actor/server/group/GroupState.scala | 136 +++++++++++++----- .../server/group/MemberCommandHandlers.scala | 94 +++--------- .../actor/server/sequence/Optimization.scala | 13 +- 7 files changed, 194 insertions(+), 218 deletions(-) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/AdminCommandHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/AdminCommandHandlers.scala index 0dbd1666bf..bf17a9b8a1 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/AdminCommandHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/AdminCommandHandlers.scala @@ -5,7 +5,7 @@ import java.time.Instant import akka.actor.Status import akka.pattern.pipe import akka.http.scaladsl.util.FastFuture -import im.actor.api.rpc.{ PeersImplicits, Update } +import im.actor.api.rpc.Update import im.actor.api.rpc.groups._ import im.actor.api.rpc.messaging.UpdateChatClear import im.actor.concurrent.FutureExt @@ -66,7 +66,9 @@ private[group] trait AdminCommandHandlers extends GroupsImplicits { val updateAdmin = UpdateGroupMemberAdminChanged(groupId, cmd.candidateUserId, isAdmin = true) val updateMembers = UpdateGroupMembersUpdated(groupId, members) // now this user is admin, change edit rules for admins - val updateCanEdit = UpdateGroupCanEditInfoChanged(groupId, canEditGroup = newState.adminSettings.canAdminsEditGroupInfo) + + val updatePermissions = permissionsUpdates(cmd.candidateUserId, newState) + // val updateCanEdit = UpdateGroupCanEditInfoChanged(groupId, canEditGroup = newState.adminSettings.canAdminsEditGroupInfo) val updateObsolete = UpdateGroupMembersUpdateObsolete(groupId, members) @@ -119,10 +121,9 @@ private[group] trait AdminCommandHandlers extends GroupsImplicits { /////////////////////////// // Groups V2 API updates // /////////////////////////// - _ ← seqUpdExt.deliverUserUpdate( - userId = cmd.candidateUserId, - update = updateCanEdit - ) + _ ← FutureExt.ftraverse(updatePermissions) { update ⇒ + seqUpdExt.deliverUserUpdate(cmd.candidateUserId, update) + } seqStateDate ← if (state.groupType.isChannel) adminCHANNELUpdates else adminGROUPUpdates } yield (members, seqStateDate) @@ -150,7 +151,9 @@ private[group] trait AdminCommandHandlers extends GroupsImplicits { val updateAdmin = UpdateGroupMemberAdminChanged(groupId, cmd.targetUserId, isAdmin = false) val updateMembers = UpdateGroupMembersUpdated(groupId, members) // now this user is not admin, change edit rules to plain members - val updateCanEdit = UpdateGroupCanEditInfoChanged(groupId, canEditGroup = newState.adminSettings.canMembersEditGroupInfo) + + val updatePermissions = permissionsUpdates(cmd.targetUserId, newState) + // val updateCanEdit = UpdateGroupCanEditInfoChanged(groupId, canEditGroup = newState.adminSettings.canMembersEditGroupInfo) val updateObsolete = UpdateGroupMembersUpdateObsolete(groupId, members) @@ -205,10 +208,9 @@ private[group] trait AdminCommandHandlers extends GroupsImplicits { /////////////////////////// // Groups V2 API updates // /////////////////////////// - _ ← seqUpdExt.deliverUserUpdate( - userId = cmd.targetUserId, - update = updateCanEdit - ) + _ ← FutureExt.ftraverse(updatePermissions) { update ⇒ + seqUpdExt.deliverUserUpdate(cmd.targetUserId, update) + } seqState ← if (state.groupType.isChannel) adminCHANNELUpdates else adminGROUPUpdates } yield seqState @@ -226,23 +228,16 @@ private[group] trait AdminCommandHandlers extends GroupsImplicits { val newState = commit(evt) val memberIds = newState.memberIds - val prevOwnerUpdates = List( - UpdateGroupCanLeaveChanged(groupId, canLeaveChanged = true), - UpdateGroupCanDeleteChanged(groupId, canDeleteChanged = false) - ) - - val newOwnerUpdates = List( - UpdateGroupCanLeaveChanged(groupId, canLeaveChanged = false), - UpdateGroupCanDeleteChanged(groupId, canDeleteChanged = true) - ) + val updatePermissionsPrevOwner = permissionsUpdates(cmd.clientUserId, newState) + val updatePermissionsNewOwner = permissionsUpdates(cmd.newOwnerId, newState) val result: Future[SeqState] = for { - // push updates to previous owner - _ ← FutureExt.ftraverse(prevOwnerUpdates) { update ⇒ + // push permission updates to previous owner + _ ← FutureExt.ftraverse(updatePermissionsPrevOwner) { update ⇒ seqUpdExt.deliverUserUpdate(cmd.clientUserId, update) } - // push updates to new owner - _ ← FutureExt.ftraverse(newOwnerUpdates) { update ⇒ + // push permission updates to new owner + _ ← FutureExt.ftraverse(updatePermissionsNewOwner) { update ⇒ seqUpdExt.deliverUserUpdate(cmd.newOwnerId, update) } seqState ← seqUpdExt.broadcastClientUpdate( @@ -262,51 +257,21 @@ private[group] trait AdminCommandHandlers extends GroupsImplicits { protected def updateAdminSettings(cmd: UpdateAdminSettings): Unit = { if (!state.permissions.canEditAdminSettings(cmd.clientUserId)) { sender() ! Status.Failure(NotAdmin) + } else if (AdminSettings.fromBitMask(cmd.settingsBitMask) == state.adminSettings) { + sender() ! UpdateAdminSettingsAck() } else { - val settOld = state.adminSettings - persist(AdminSettingsUpdated(Instant.now, cmd.settingsBitMask)) { evt ⇒ val newState = commit(evt) - val settNew = newState.adminSettings - - val (membersUpdates, adminsUpdates) = { - // push to all members except admins and owner - val showAdminToMembers = PartialFunction.condOpt(settOld.showAdminsToMembers != settNew.showAdminsToMembers) { - case true ⇒ UpdateGroupCanViewAdminsChanged(groupId, canViewAdmins = settNew.showAdminsToMembers) - } - - // push to all members except admins and owner - val canMembersInvite = PartialFunction.condOpt(settOld.canMembersInvite != settNew.canMembersInvite) { - case true ⇒ UpdateGroupCanInviteMembersChanged(groupId, canInviteMembers = settNew.canMembersInvite) - } - - // push to all members except admins and owner - val canMembersEditGroupInfo = PartialFunction.condOpt(settOld.canMembersEditGroupInfo != settNew.canMembersEditGroupInfo) { - case true ⇒ UpdateGroupCanEditInfoChanged(groupId, canEditGroup = settNew.canMembersEditGroupInfo) - } - - // push to admins only - val canAdminsEditGroupInfo = PartialFunction.condOpt(settOld.canAdminsEditGroupInfo != settNew.canAdminsEditGroupInfo) { - case true ⇒ UpdateGroupCanEditInfoChanged(groupId, canEditGroup = settNew.canAdminsEditGroupInfo) - } - - ( - List(showAdminToMembers, canMembersInvite, canMembersEditGroupInfo).flatten[Update], - List(canAdminsEditGroupInfo).flatten[Update] - ) - } - - val plainMemberIds = (newState.memberIds - newState.ownerUserId) -- newState.adminIds - val adminsOnlyIds = newState.adminIds - newState.ownerUserId + //TODO: check if settings actually changed val result: Future[UpdateAdminSettingsAck] = for { - // deliver updates about settings changed to plain group members - _ ← FutureExt.ftraverse(membersUpdates) { update ⇒ - seqUpdExt.broadcastPeopleUpdate(userIds = plainMemberIds, update) - } - // deliver updates about settings changed to plain group members - _ ← FutureExt.ftraverse(membersUpdates) { update ⇒ - seqUpdExt.broadcastPeopleUpdate(userIds = adminsOnlyIds, update) + // deliver permissions updates to all group members + // maybe there is no need to generate updates for each user + // three parts: members, admins, owner should be enough + _ ← FutureExt.ftraverse(newState.memberIds.toSeq) { userId ⇒ + FutureExt.ftraverse(permissionsUpdates(userId, newState)) { update ⇒ + seqUpdExt.deliverUserUpdate(userId, update) + } } } yield UpdateAdminSettingsAck() @@ -349,28 +314,22 @@ private[group] trait AdminCommandHandlers extends GroupsImplicits { val dateMillis = evt.ts.toEpochMilli val randomId = ACLUtils.randomLong() - // TODO: add UpdateIsDeleted - val deleteGroupMembersUpdates: Vector[Update] = Vector( - UpdateGroupCanSendMessagesChanged(groupId, canSendMessages = false), - UpdateGroupCanViewMembersChanged(groupId, canViewMembers = false), - UpdateGroupCanEditInfoChanged(groupId, canEditGroup = false), - UpdateGroupCanEditUsernameChanged(groupId, canEditUsername = false), - UpdateGroupCanEditAdminsChanged(groupId, canAssignAdmins = false), - UpdateGroupCanViewAdminsChanged(groupId, canViewAdmins = false), - UpdateGroupCanEditAdminSettingsChanged(groupId, canEditAdminSettings = false), - UpdateGroupCanInviteMembersChanged(groupId, canInviteMembers = false), - UpdateGroupCanInviteViaLink(groupId, canInviteViaLink = false), - UpdateGroupCanLeaveChanged(groupId, canLeaveChanged = false), - UpdateGroupCanDeleteChanged(groupId, canDeleteChanged = false), - UpdateGroupMemberChanged(groupId, isMember = false), - // if channel, or group is big enough - if (newState.groupType.isChannel) - UpdateGroupMembersCountChanged(groupId, membersCount = 0) - else - UpdateGroupMembersUpdated(groupId, members = Vector.empty), - UpdateGroupDeleted(groupId) + val emptyPermissions = Vector( + UpdateGroupPermissionsChanged(groupId, newState.permissions.GroupEmpty), + UpdateGroupFullPermissionsChanged(groupId, newState.permissions.FullGroupEmpty) ) + val deleteGroupMembersUpdates: Vector[Update] = emptyPermissions ++ + Vector( + UpdateGroupMemberChanged(groupId, isMember = false), + // if channel, or group is big enough + if (newState.groupType.isChannel) + UpdateGroupMembersCountChanged(groupId, membersCount = 0) + else + UpdateGroupMembersUpdated(groupId, members = Vector.empty), + UpdateGroupDeleted(groupId) + ) + //TODO: remove deprecated. GroupInviteTokenRepo don't have replacement yet. newState.memberIds foreach { userId ⇒ db.run( diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala index 86fd4d7541..6ccef21e6c 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala @@ -5,6 +5,7 @@ import java.time.Instant import akka.actor.Status import akka.http.scaladsl.util.FastFuture import akka.pattern.pipe +import im.actor.api.rpc.Update import im.actor.api.rpc.groups._ import im.actor.api.rpc.users.ApiSex import im.actor.concurrent.FutureExt @@ -163,6 +164,12 @@ private[group] trait GroupCommandHandlers } } + protected def permissionsUpdates(userId: Int, currState: GroupState): Vector[Update] = + Vector( + UpdateGroupPermissionsChanged(groupId, currState.permissions.groupFor(userId)), + UpdateGroupFullPermissionsChanged(groupId, currState.permissions.fullFor(userId)) + ) + protected def isValidTitle(title: String) = title.nonEmpty && title.length < 255 } diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala index fd54c7dfc8..6b3c36c856 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala @@ -8,7 +8,7 @@ import akka.http.scaladsl.util.FastFuture import im.actor.api.rpc.peers.{ ApiPeer, ApiPeerType } import im.actor.concurrent.ActorFutures import im.actor.serialization.ActorSerializer -import im.actor.server.cqrs.{ Processor, TaggedEvent } +import im.actor.server.cqrs.{ Event, Processor, TaggedEvent } import im.actor.server.db.DbExtension import im.actor.server.dialog.{ DialogEnvelope, DialogExtension } import im.actor.server.group.GroupErrors._ @@ -184,6 +184,23 @@ private[group] final class GroupProcessor case LoadAdminSettings(clientUserId) ⇒ loadAdminSettings(clientUserId) } + override def afterCommit(e: Event) = { + super.afterCommit(e) + if (state.membersCount > 25) { + updateCanCall(state) + } + // TODO: add async members + // if(state.membersCount > 50) { updateMembersAsync(state) } + } + + private def updateCanCall(state: GroupState): Unit = { + state.memberIds foreach { userId ⇒ + permissionsUpdates(userId, state) foreach { update ⇒ + seqUpdExt.deliverUserUpdate(userId, update) + } + } + } + def persistenceId: String = GroupProcessor.persistenceIdFor(groupId) protected def getInitialState: GroupState = GroupState.empty diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala index cfde4c1f59..ddfb122cdf 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala @@ -35,8 +35,7 @@ trait GroupQueryHandlers { } } - //TODO: do something with this method. Will this method used in "client" context. - // If not - don't change it. Maybe rename to `getMembersInternal` + //TODO: This is internal server API. Properly name it, for example `getMembersInternal` protected def getMembers: Future[GetMembersResponse] = FastFuture.successful { GetMembersResponse( @@ -108,7 +107,7 @@ trait GroupQueryHandlers { case Channel ⇒ ApiGroupType.CHANNEL case General | Unrecognized(_) ⇒ ApiGroupType.GROUP }), - canSendMessage = Some(state.permissions.canSendMessage(clientUserId)), + permissions = Some(state.permissions.groupFor(clientUserId)), isDeleted = Some(state.isDeleted) ) ) @@ -126,20 +125,11 @@ trait GroupQueryHandlers { ownerUserId = state.getShowableOwner(clientUserId), createDate = extractCreatedAtMillis(state), ext = None, - canViewMembers = Some(state.permissions.canViewMembers(clientUserId)), - canInvitePeople = Some(state.permissions.canInvitePeople(clientUserId)), isSharedHistory = Some(state.isHistoryShared), isAsyncMembers = Some(state.isAsyncMembers), members = membersAndCount(state, clientUserId)._1, shortName = state.shortName, - canEditGroupInfo = Some(state.permissions.canEditInfo(clientUserId)), - canEditShortName = Some(state.permissions.canEditShortName(clientUserId)), - canEditAdminList = Some(state.permissions.canEditAdmins(clientUserId)), - canViewAdminList = Some(state.permissions.canViewAdmins(clientUserId)), - canEditAdminSettings = Some(state.permissions.canEditAdminSettings(clientUserId)), - canInviteViaLink = Some(state.permissions.canInviteViaLink(clientUserId)), - canDelete = Some(state.permissions.canDelete(clientUserId)), - canLeave = Some(state.permissions.canLeave(clientUserId)) + permissions = Some(state.permissions.fullFor(clientUserId)) ) ) } diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala index 7ee9b0da92..96b26079ce 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala @@ -296,13 +296,35 @@ private[group] final case class GroupState( def withSnapshot(metadata: SnapshotMetadata, snapshot: Any): GroupState = this object permissions { + val GroupEmpty = 0L + val FullGroupEmpty = 0L + + /////////////////////////// + // General permissions // + /////////////////////////// + + /** + * @note check up to date doc in im.actor.api.rpc.groups.ApiGroup + * + * Group permissions bits: + * 0 - canSendMessage. Default is FALSE. + * 1 - canClear. Default is FALSE. + * 2 - canLeave. Default is FALSE. + * 3 - canDelete. Default is FALSE. + */ + def groupFor(userId: Int): Long = { + ((toInt(canSendMessage(userId)) << 0) + + (toInt(canClear(userId)) << 1) + + (toInt(canLeave(userId)) << 2) + + (toInt(canDelete(userId)) << 3)).toLong + } /** * bot can send messages in all groups * in general/public group only members can send messages * in channels only owner and admins can send messages */ - def canSendMessage(clientUserId: Int) = + private def canSendMessage(clientUserId: Int) = { groupType match { case General ⇒ isMember(clientUserId) @@ -310,6 +332,57 @@ private[group] final case class GroupState( } } || bot.exists(_.userId == clientUserId) + // if history shared, only owner can clear, everyone otherwise + private def canClear(clientUserId: Int): Boolean = !isHistoryShared || isOwner(clientUserId) + + /** + * for now, owner can't leave group. + * He can either transfer ownership and leave group + * or delete group completely. + */ + def canLeave(clientUserId: Int): Boolean = !isOwner(clientUserId) + + // only owner can delete group + def canDelete(clientUserId: Int): Boolean = isOwner(clientUserId) + + //////////////////////////// + // Full group permissions // + //////////////////////////// + + /** + * @note check up to date doc at im.actor.api.rpc.groups.ApiGroupFull + * + * Full group permissions bits: + * 0 - canEditInfo. Default is FALSE. + * 1 - canViewMembers. Default is FALSE. + * 2 - canInviteMembers. Default is FALSE. + * 3 - canInviteViaLink. Default is FALSE. + * 4 - canCall. Default is FALSE. + * 5 - canEditAdminSettings. Default is FALSE. + * 6 - canViewAdmins. Default is FALSE. + * 7 - canEditAdmins. Default is FALSE. + */ + def fullFor(userId: Int): Long = { + ((toInt(canEditInfo(userId)) << 0) + + (toInt(canViewMembers(userId)) << 1) + + (toInt(canInviteMembers(userId)) << 2) + + (toInt(canInviteViaLink(userId)) << 3) + + (toInt(canCall(userId)) << 4) + + (toInt(canEditAdminSettings(userId)) << 5) + + (toInt(canViewAdmins(userId)) << 6) + + (toInt(canEditAdmins(userId)) << 7)).toLong + } + + /** + * owner always can edit group info + * admin can edit group info, if canAdminsEditGroupInfo is true in admin settings + * any member can edit group info, if canMembersEditGroupInfo is true in admin settings + */ + def canEditInfo(clientUserId: Int): Boolean = + isOwner(clientUserId) || + (isAdmin(clientUserId) && adminSettings.canAdminsEditGroupInfo) || + (isMember(clientUserId) && adminSettings.canMembersEditGroupInfo) + /** * in general/public group, all members can view members * in channels, owner and admins can view members @@ -321,10 +394,10 @@ private[group] final case class GroupState( } /** - * owner and admins always can invite new people - * members can invite new people if canMembersInvite is true + * owner and admins always can invite new members + * regular members can invite new members if adminSettings.canMembersInvite is true */ - def canInvitePeople(clientUserId: Int) = + def canInviteMembers(clientUserId: Int) = isOwner(clientUserId) || isAdmin(clientUserId) || (isMember(clientUserId) && adminSettings.canMembersInvite) @@ -332,54 +405,43 @@ private[group] final case class GroupState( /** * only owner and admins can invite via link */ - def canInviteViaLink(clientUserId: Int) = - isOwner(clientUserId) || isAdmin(clientUserId) - - /** - * owner and admins can kick members - */ - def canKickMember(clientUserId: Int) = - isOwner(clientUserId) || isAdmin(clientUserId) + private def canInviteViaLink(clientUserId: Int) = isOwner(clientUserId) || isAdmin(clientUserId) /** - * owner always can edit group info - * admin can edit group info, if canAdminsEditGroupInfo is true in admin settings - * any member can edit group info, if canMembersEditGroupInfo is true in admin settings + * All members can call, if group has less than 25 members, and is not a channel */ - def canEditInfo(clientUserId: Int): Boolean = - isOwner(clientUserId) || - (isAdmin(clientUserId) && adminSettings.canAdminsEditGroupInfo) || - (isMember(clientUserId) && adminSettings.canMembersEditGroupInfo) - - // only owner can change short name - def canEditShortName(clientUserId: Int): Boolean = isOwner(clientUserId) + private def canCall(clientUserId: Int) = !groupType.isChannel && membersCount <= 25 - // only owner can make history shared - def canMakeHistoryShared(clientUserId: Int): Boolean = isOwner(clientUserId) - - // only owner and other admins can edit admins list - def canEditAdmins(clientUserId: Int): Boolean = - isOwner(clientUserId) || isAdmin(clientUserId) + // only owner can change admin settings + def canEditAdminSettings(clientUserId: Int): Boolean = isOwner(clientUserId) /** * admins list is always visible to owner and admins * admins list is visible to any member if showAdminsToMembers = true */ - def canViewAdmins(clientUserId: Int): Boolean = + private def canViewAdmins(clientUserId: Int): Boolean = isOwner(clientUserId) || isAdmin(clientUserId) || adminSettings.showAdminsToMembers - // only owner can change admin settings - def canEditAdminSettings(clientUserId: Int): Boolean = isOwner(clientUserId) + // only owner and other admins can edit admins list + def canEditAdmins(clientUserId: Int): Boolean = + isOwner(clientUserId) || isAdmin(clientUserId) - // only owner can delete group - def canDelete(clientUserId: Int): Boolean = isOwner(clientUserId) + //////////////////////////// + // Internal permissions // + //////////////////////////// /** - * for now, owner can't leave group. - * He can either transfer ownership and leave group - * or delete group completely. + * owner and admins can kick members */ - def canLeave(clientUserId: Int): Boolean = !isOwner(clientUserId) + def canKickMember(clientUserId: Int) = + isOwner(clientUserId) || isAdmin(clientUserId) + + // only owner can change short name + def canEditShortName(clientUserId: Int): Boolean = isOwner(clientUserId) + + // only owner can make history shared + def canMakeHistoryShared(clientUserId: Int): Boolean = isOwner(clientUserId) + private def toInt(b: Boolean) = if (b) 1 else 0 } } diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/MemberCommandHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/MemberCommandHandlers.scala index b4df43f301..3fd617a513 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/MemberCommandHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/MemberCommandHandlers.scala @@ -23,7 +23,7 @@ private[group] trait MemberCommandHandlers extends GroupsImplicits { import im.actor.server.ApiConversions._ protected def invite(cmd: Invite): Unit = { - if (!state.permissions.canInvitePeople(cmd.inviterUserId)) { + if (!state.permissions.canInviteMembers(cmd.inviterUserId)) { sender() ! noPermission } else if (state.isInvited(cmd.inviteeUserId)) { sender() ! Status.Failure(GroupErrors.UserAlreadyInvited) @@ -40,7 +40,7 @@ private[group] trait MemberCommandHandlers extends GroupsImplicits { val apiMembers = newState.members.values.map(_.asStruct).toVector // if user ever been in this group - we should push these updates, - val inviteeUpdatesNew: List[Update] = refreshGroupUpdates(newState, cmd.inviteeUserId) + val inviteeUpdatesNew: Vector[Update] = refreshGroupUpdates(newState, cmd.inviteeUserId) val membersUpdateNew: Update = if (newState.groupType.isChannel) // if channel, or group is big enough @@ -220,8 +220,8 @@ private[group] trait MemberCommandHandlers extends GroupsImplicits { // // If user was invited to group by other member - we don't need to push group updates, // cause they we pushed already on invite step - val joiningUserUpdatesNew: List[Update] = - if (wasInvited) List.empty[Update] else refreshGroupUpdates(newState, cmd.joiningUserId) + val joiningUserUpdatesNew: Vector[Update] = + if (wasInvited) Vector.empty[Update] else refreshGroupUpdates(newState, cmd.joiningUserId) val membersUpdateNew: Update = if (newState.groupType.isChannel) // if channel, or group is big enough @@ -340,38 +340,15 @@ private[group] trait MemberCommandHandlers extends GroupsImplicits { } else if (!state.permissions.canLeave(cmd.userId)) { sender() ! Status.Failure(CantLeaveGroup) } else { - persist(UserLeft(Instant.now, cmd.userId)) { evt ⇒ + val leftEvent = UserLeft(Instant.now, cmd.userId) + persist(leftEvent) { evt ⇒ // no commit here. it will be after service message sent val dateMillis = evt.ts.toEpochMilli val updateObsolete = UpdateGroupUserLeaveObsolete(groupId, cmd.userId, dateMillis, cmd.randomId) - val leftUserUpdatesNew: Vector[Update] = { - val commonUpdates = Vector( - UpdateGroupCanSendMessagesChanged(groupId, canSendMessages = false), - UpdateGroupCanEditInfoChanged(groupId, canEditGroup = false), - UpdateGroupCanEditUsernameChanged(groupId, canEditUsername = false), - UpdateGroupCanEditAdminsChanged(groupId, canAssignAdmins = false), - UpdateGroupCanViewAdminsChanged(groupId, canViewAdmins = false), - UpdateGroupCanEditAdminSettingsChanged(groupId, canEditAdminSettings = false), - UpdateGroupCanInviteViaLink(groupId, canInviteViaLink = false), - UpdateGroupCanLeaveChanged(groupId, canLeaveChanged = false), - UpdateGroupCanDeleteChanged(groupId, canDeleteChanged = false) - ) - - if (state.groupType.isChannel) { - (UpdateGroupCanViewMembersChanged(groupId, canViewMembers = false) +: - commonUpdates) :+ - UpdateGroupCanInviteMembersChanged(groupId, canInviteMembers = false) - } else { - commonUpdates ++ Vector( - UpdateGroupCanViewMembersChanged(groupId, canViewMembers = false), - UpdateGroupMembersUpdated(groupId, members = Vector.empty), - UpdateGroupCanInviteMembersChanged(groupId, canInviteMembers = false) - ) - } - } + val updatePermissions = permissionsUpdates(cmd.userId, currState = state.updated(leftEvent)) val membersUpdateNew = if (state.groupType.isChannel) { // if channel, or group is big enough @@ -424,7 +401,8 @@ private[group] trait MemberCommandHandlers extends GroupsImplicits { // push left user updates // • with empty group members // • that he can't view and invite members - _ ← FutureExt.ftraverse(leftUserUpdatesNew) { update ⇒ + leftUpdates = updatePermissions :+ UpdateGroupMembersUpdated(groupId, members = Vector.empty) + _ ← FutureExt.ftraverse(leftUpdates) { update ⇒ seqUpdExt.deliverUserUpdate(userId = cmd.userId, update) } } yield SeqStateDate(seq, state, date) @@ -445,7 +423,8 @@ private[group] trait MemberCommandHandlers extends GroupsImplicits { ) // push left user updates that he has no group rights - _ ← FutureExt.ftraverse(leftUserUpdatesNew) { update ⇒ + leftUpdates = updatePermissions :+ UpdateGroupMembersCountChanged(groupId, membersCount = 0) + _ ← FutureExt.ftraverse(leftUpdates) { update ⇒ seqUpdExt.deliverUserUpdate(userId = cmd.userId, update) } } yield SeqStateDate(seq, state, dateMillis) @@ -492,30 +471,7 @@ private[group] trait MemberCommandHandlers extends GroupsImplicits { val updateObsolete = UpdateGroupUserKickObsolete(groupId, cmd.kickedUserId, cmd.kickerUserId, dateMillis, cmd.randomId) - val kickedUserUpdatesNew: Vector[Update] = { - val commonUpdates = Vector( - UpdateGroupCanViewMembersChanged(groupId, canViewMembers = false), - UpdateGroupCanSendMessagesChanged(groupId, canSendMessages = false), - UpdateGroupCanEditInfoChanged(groupId, canEditGroup = false), - UpdateGroupCanEditUsernameChanged(groupId, canEditUsername = false), - UpdateGroupCanEditAdminsChanged(groupId, canAssignAdmins = false), - UpdateGroupCanViewAdminsChanged(groupId, canViewAdmins = false), - UpdateGroupCanEditAdminSettingsChanged(groupId, canEditAdminSettings = false), - UpdateGroupCanInviteMembersChanged(groupId, canInviteMembers = false), - UpdateGroupCanInviteViaLink(groupId, canInviteViaLink = false), - UpdateGroupCanLeaveChanged(groupId, canLeaveChanged = false), - UpdateGroupCanDeleteChanged(groupId, canDeleteChanged = false) - ) - - if (state.groupType.isChannel) { - commonUpdates :+ UpdateGroupMemberChanged(groupId, isMember = false) - } else { - commonUpdates ++ Vector( - UpdateGroupMembersUpdated(groupId, members = Vector.empty), - UpdateGroupMemberChanged(groupId, isMember = false) - ) - } - } + val updatePermissions = permissionsUpdates(cmd.kickedUserId, newState) val membersUpdateNew: Update = if (newState.groupType.isChannel) { // if channel, or group is big enough @@ -563,7 +519,11 @@ private[group] trait MemberCommandHandlers extends GroupsImplicits { // • with empty group members // • that he is no longer a member of group // • that he can't view and invite members - _ ← FutureExt.ftraverse(kickedUserUpdatesNew) { update ⇒ + kickedUserUpdates = updatePermissions ++ Vector( + UpdateGroupMembersUpdated(groupId, members = Vector.empty), + UpdateGroupMemberChanged(groupId, isMember = false) + ) + _ ← FutureExt.ftraverse(kickedUserUpdates) { update ⇒ seqUpdExt.deliverUserUpdate(userId = cmd.kickedUserId, update) } } yield SeqStateDate(seq, state, date) @@ -591,7 +551,8 @@ private[group] trait MemberCommandHandlers extends GroupsImplicits { ) // push kicked user updates that he has no group rights - _ ← FutureExt.ftraverse(kickedUserUpdatesNew) { update ⇒ + kickedUserUpdates = updatePermissions :+ UpdateGroupMemberChanged(groupId, isMember = false) + _ ← FutureExt.ftraverse(kickedUserUpdates) { update ⇒ seqUpdExt.deliverUserUpdate(userId = cmd.kickedUserId, update) } } yield SeqStateDate(seq, state, dateMillis) @@ -626,27 +587,16 @@ private[group] trait MemberCommandHandlers extends GroupsImplicits { // Updates that will be sent to user, when he enters group. // Helps clients that have this group to refresh it's data. - private def refreshGroupUpdates(newState: GroupState, userId: Int): List[Update] = List( + private def refreshGroupUpdates(newState: GroupState, userId: Int): Vector[Update] = Vector( UpdateGroupMemberChanged(groupId, isMember = true), UpdateGroupAboutChanged(groupId, newState.about), UpdateGroupAvatarChanged(groupId, newState.avatar), UpdateGroupTopicChanged(groupId, newState.topic), UpdateGroupTitleChanged(groupId, newState.title), - UpdateGroupOwnerChanged(groupId, newState.ownerUserId), - UpdateGroupCanSendMessagesChanged(groupId, newState.permissions.canSendMessage(userId)), - UpdateGroupCanViewMembersChanged(groupId, newState.permissions.canViewMembers(userId)), - UpdateGroupCanInviteMembersChanged(groupId, newState.permissions.canInvitePeople(userId)), - UpdateGroupCanEditInfoChanged(groupId, newState.permissions.canEditInfo(userId)), - UpdateGroupCanEditUsernameChanged(groupId, newState.permissions.canEditShortName(userId)), - UpdateGroupCanEditAdminsChanged(groupId, newState.permissions.canEditAdmins(userId)), - UpdateGroupCanViewAdminsChanged(groupId, newState.permissions.canViewAdmins(userId)), - UpdateGroupCanInviteViaLink(groupId, newState.permissions.canInviteViaLink(userId)), - UpdateGroupCanLeaveChanged(groupId, newState.permissions.canLeave(userId)), - UpdateGroupCanDeleteChanged(groupId, newState.permissions.canDelete(userId)), - UpdateGroupCanEditAdminSettingsChanged(groupId, newState.permissions.canEditAdminSettings(userId)) + UpdateGroupOwnerChanged(groupId, newState.ownerUserId) // UpdateGroupExtChanged(groupId, newState.extension) //TODO: figure out and fix // if(bigGroup) UpdateGroupMembersCountChanged(groupId, newState.extension) - ) + ) ++ permissionsUpdates(userId, newState) private def serviceMessageUpdate(senderUserId: Int, date: Long, randomId: Long, message: ApiServiceMessage) = UpdateMessage( diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/sequence/Optimization.scala b/actor-server/actor-core/src/main/scala/im/actor/server/sequence/Optimization.scala index e74b725349..5e6df967d1 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/sequence/Optimization.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/sequence/Optimization.scala @@ -36,9 +36,6 @@ object Optimization extends MessageParsing { UpdateGroupOwnerChanged.header, UpdateGroupHistoryShared.header, - UpdateGroupCanSendMessagesChanged.header, - UpdateGroupCanViewMembersChanged.header, - UpdateGroupCanInviteMembersChanged.header, UpdateGroupMemberChanged.header, UpdateGroupMembersBecameAsync.header, UpdateGroupMembersUpdated.header, @@ -46,14 +43,8 @@ object Optimization extends MessageParsing { UpdateGroupMembersCountChanged.header, UpdateGroupMemberAdminChanged.header, UpdateGroupShortNameChanged.header, - UpdateGroupCanEditInfoChanged.header, - UpdateGroupCanEditUsernameChanged.header, - UpdateGroupCanEditAdminsChanged.header, - UpdateGroupCanViewAdminsChanged.header, - UpdateGroupCanEditAdminSettingsChanged.header, - UpdateGroupCanInviteViaLink.header, - UpdateGroupCanLeaveChanged.header, - UpdateGroupCanDeleteChanged.header + UpdateGroupFullPermissionsChanged.header, + UpdateGroupPermissionsChanged.header ) if (deliveryTag == GroupV2) emptyUpdate From 405aa1aae738445c5aa9086799ee5fa0fe632285 Mon Sep 17 00:00:00 2001 From: rockjam Date: Thu, 21 Jul 2016 21:17:00 +0300 Subject: [PATCH 175/414] chore(server): update actor-commons 0.0.18 -> 0.0.19 --- actor-server/project/Dependencies.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actor-server/project/Dependencies.scala b/actor-server/project/Dependencies.scala index 26cb76681b..e466c43c8c 100644 --- a/actor-server/project/Dependencies.scala +++ b/actor-server/project/Dependencies.scala @@ -4,7 +4,7 @@ import sbt._ object Dependencies { object V { - val actorCommons = "0.0.18" + val actorCommons = "0.0.19" val actorBotkit = "1.0.109" val akka = "2.4.7" val akkaHttpJson = "1.5.0" From 080af4f6fe2eed506692277b1be35d6cbf5bf8e2 Mon Sep 17 00:00:00 2001 From: rockjam Date: Thu, 21 Jul 2016 22:43:54 +0300 Subject: [PATCH 176/414] fix(server:groups): execute hook after state commit only if actor already recovered --- .../scala/im/actor/server/group/GroupProcessor.scala | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala index 6b3c36c856..3c7488b586 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala @@ -186,11 +186,13 @@ private[group] final class GroupProcessor override def afterCommit(e: Event) = { super.afterCommit(e) - if (state.membersCount > 25) { - updateCanCall(state) + if (recoveryFinished) { + if (state.membersCount > 25) { + updateCanCall(state) + } + // TODO: add async members + // if(state.membersCount > 50) { updateMembersAsync(state) } } - // TODO: add async members - // if(state.membersCount > 50) { updateMembersAsync(state) } } private def updateCanCall(state: GroupState): Unit = { From 2b1f7f5027926b3ac5525524701acaa0d068bd2b Mon Sep 17 00:00:00 2001 From: rockjam Date: Fri, 22 Jul 2016 02:50:59 +0300 Subject: [PATCH 177/414] =?UTF-8?q?feat(server:search):=20=09=E2=80=A2=20p?= =?UTF-8?q?ut=20@=20before=20partial=20global=20name=20search=20result;=20?= =?UTF-8?q?=09=E2=80=A2=20filter=20out=20duplicated=20peers=20found=20-=20?= =?UTF-8?q?local=20peer=20is=20priority?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/search/SearchServiceImpl.scala | 35 +++++++++++-------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/search/SearchServiceImpl.scala b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/search/SearchServiceImpl.scala index 88064e02ac..16ce7852a1 100644 --- a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/search/SearchServiceImpl.scala +++ b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/search/SearchServiceImpl.scala @@ -40,17 +40,18 @@ class SearchServiceImpl(implicit system: ActorSystem) extends SearchService { case ((pts, txts), ApiSearchPeerTypeCondition(pt)) ⇒ (pts + pt, txts) case ((pts, txts), _) ⇒ (pts, txts) } + val peerTypesSorted = peerTypes.toVector.sortBy(_.id) texts.toList match { case text :: Nil if text.length < 3 ⇒ FastFuture.successful(Ok(EmptyResult)) case text :: Nil ⇒ val tps = if (peerTypes.isEmpty) - Set(ApiSearchPeerType.Public, ApiSearchPeerType.Contacts, ApiSearchPeerType.Groups) + Vector(ApiSearchPeerType.Groups, ApiSearchPeerType.Contacts, ApiSearchPeerType.Public) else - peerTypes - searchResult(tps.toVector, Some(text), optimizations) - case Nil ⇒ searchResult(peerTypes.toVector, None, optimizations) + peerTypesSorted + searchResult(tps, Some(text), optimizations) + case Nil ⇒ searchResult(peerTypesSorted, None, optimizations) case _ ⇒ FastFuture.successful(Error(RpcError(400, "INVALID_QUERY", "Invalid query.", canTryAgain = false, None))) } } @@ -77,11 +78,15 @@ class SearchServiceImpl(implicit system: ActorSystem) extends SearchService { )(implicit client: AuthorizedClientData): Future[HandlerResult[ResponsePeerSearch]] = { for { results ← FutureExt.ftraverse(pts)(search(_, text)).map(_.reduce(_ ++ _)) - (groupIds, userIds) = results.view.map(_.peer).foldLeft(Vector.empty[Int], Vector.empty[Int]) { - case ((gids, uids), ApiPeer(pt, pid)) ⇒ - pt match { - case ApiPeerType.Private ⇒ (gids, uids :+ pid) - case ApiPeerType.Group ⇒ (gids :+ pid, uids) + (groupIds, userIds, searchResults) = (results foldLeft (Vector.empty[Int], Vector.empty[Int], Vector.empty[ApiPeerSearchResult])) { + case (acc @ (gids, uids, rslts), found @ ApiPeerSearchResult(peer, _)) ⇒ + if (rslts.exists(_.peer == peer)) { + acc + } else { + peer.`type` match { + case ApiPeerType.Private ⇒ (gids, uids :+ peer.id, rslts :+ found) + case ApiPeerType.Group ⇒ (gids :+ peer.id, uids, rslts :+ found) + } } } // TODO: make like here: im.actor.server.api.rpc.service.groups.GroupsServiceImpl.usersOrPeers @@ -89,7 +94,7 @@ class SearchServiceImpl(implicit system: ActorSystem) extends SearchService { } yield { val stripEntities = optimizations.contains(ApiUpdateOptimization.STRIP_ENTITIES) Ok(ResponsePeerSearch( - searchResults = results, + searchResults = searchResults, users = if (stripEntities) Vector.empty else users.toVector, groups = if (stripEntities) Vector.empty else groups.toVector, userPeers = users.toVector map (u ⇒ ApiUserOutPeer(u.id, u.accessHash)), @@ -149,10 +154,10 @@ class SearchServiceImpl(implicit system: ActorSystem) extends SearchService { // search users by nickname prefix private def searchGlobalUsersPrefix(text: Option[String])(implicit client: AuthorizedClientData): Future[IndexedSeq[PeerAndMatchString]] = { text map { query ⇒ - globalNamesStorage.userIdsByPrefix(query) map { results ⇒ + globalNamesStorage.userIdsByPrefix(normName(query)) map { results ⇒ results collect { case (userId, nickName) if userId != client.userId ⇒ - ApiPeer(ApiPeerType.Private, userId) → nickName + ApiPeer(ApiPeerType.Private, userId) → s"@$nickName" } } } getOrElse FastFuture.successful(Vector.empty) @@ -161,15 +166,17 @@ class SearchServiceImpl(implicit system: ActorSystem) extends SearchService { // find groups by global name prefix private def searchGlobalGroupsPrefix(text: Option[String]): Future[IndexedSeq[PeerAndMatchString]] = { text map { query ⇒ - globalNamesStorage.groupIdsByPrefix(query) map { results ⇒ + globalNamesStorage.groupIdsByPrefix(normName(query)) map { results ⇒ results map { case (groupId, globalName) ⇒ - ApiPeer(ApiPeerType.Group, groupId) → globalName + ApiPeer(ApiPeerType.Group, groupId) → s"@$globalName" } } } getOrElse FastFuture.successful(Vector.empty) } + private def normName(n: String) = if (n.startsWith("@")) n.drop(1) else n + private def searchContacts(text: Option[String])(implicit client: AuthorizedClientData): Future[IndexedSeq[ApiPeer]] = { for { userIds ← db.run(UserContactRepo.findContactIdsActive(client.userId)) From 17976b47e443c9d7de530206f3b7c952e555dde6 Mon Sep 17 00:00:00 2001 From: rockjam Date: Fri, 22 Jul 2016 03:29:57 +0300 Subject: [PATCH 178/414] =?UTF-8?q?fix(server:groups):=20=09=E2=80=A2=20co?= =?UTF-8?q?rrect=20updates=20on=20group=20deletion;=20=09=E2=80=A2=20relea?= =?UTF-8?q?se=20global=20name=20on=20group=20deletion;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../server/group/AdminCommandHandlers.scala | 39 ++++++++++++------- .../im/actor/server/group/GroupState.scala | 2 - .../server/names/GlobalNamesStorage.scala | 1 + 3 files changed, 27 insertions(+), 15 deletions(-) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/AdminCommandHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/AdminCommandHandlers.scala index bf17a9b8a1..8254b34115 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/AdminCommandHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/AdminCommandHandlers.scala @@ -14,6 +14,7 @@ import im.actor.server.acl.ACLUtils import im.actor.server.group.GroupCommands.{ DeleteGroup, DismissUserAdmin, MakeHistoryShared, MakeUserAdmin, RevokeIntegrationToken, RevokeIntegrationTokenAck, TransferOwnership, UpdateAdminSettings, UpdateAdminSettingsAck } import im.actor.server.group.GroupErrors.{ NotAMember, NotAdmin, UserAlreadyAdmin, UserAlreadyNotAdmin } import im.actor.server.group.GroupEvents.{ AdminSettingsUpdated, AdminStatusChanged, GroupDeleted, HistoryBecameShared, IntegrationTokenRevoked, OwnerChanged } +import im.actor.server.names.{ GlobalNameOwner, OwnerType } import im.actor.server.persist.{ GroupBotRepo, GroupInviteTokenRepo, GroupUserRepo, HistoryMessageRepo } import im.actor.server.sequence.{ SeqState, SeqStateDate } @@ -308,22 +309,27 @@ private[group] trait AdminCommandHandlers extends GroupsImplicits { if (!state.permissions.canDelete(cmd.clientUserId)) { sender() ! noPermission } else { + val exMemberIds = state.memberIds + val exGlobalName = state.shortName + val exGroupType = state.groupType + persist(GroupDeleted(Instant.now, cmd.clientUserId)) { evt ⇒ - val newState = commit(evt) + commit(evt) val dateMillis = evt.ts.toEpochMilli val randomId = ACLUtils.randomLong() + val ZeroPermissions = 0L val emptyPermissions = Vector( - UpdateGroupPermissionsChanged(groupId, newState.permissions.GroupEmpty), - UpdateGroupFullPermissionsChanged(groupId, newState.permissions.FullGroupEmpty) + UpdateGroupPermissionsChanged(groupId, ZeroPermissions), + UpdateGroupFullPermissionsChanged(groupId, ZeroPermissions) ) val deleteGroupMembersUpdates: Vector[Update] = emptyPermissions ++ Vector( UpdateGroupMemberChanged(groupId, isMember = false), // if channel, or group is big enough - if (newState.groupType.isChannel) + if (exGroupType.isChannel) UpdateGroupMembersCountChanged(groupId, membersCount = 0) else UpdateGroupMembersUpdated(groupId, members = Vector.empty), @@ -331,7 +337,7 @@ private[group] trait AdminCommandHandlers extends GroupsImplicits { ) //TODO: remove deprecated. GroupInviteTokenRepo don't have replacement yet. - newState.memberIds foreach { userId ⇒ + exMemberIds foreach { userId ⇒ db.run( for { _ ← GroupUserRepo.delete(groupId, userId) @@ -341,16 +347,17 @@ private[group] trait AdminCommandHandlers extends GroupsImplicits { } val result: Future[SeqState] = for { - _ ← db.run(HistoryMessageRepo.deleteAll(cmd.clientUserId, apiGroupPeer.asModel)) + // release global name of group + _ ← globalNamesStorage.updateOrRemove(exGlobalName, newGlobalName = None, GlobalNameOwner(OwnerType.Group, groupId)) /////////////////////////// // Groups V1 API updates // /////////////////////////// // push all members updates about other members left group - _ ← FutureExt.ftraverse(newState.memberIds.toSeq) { userId ⇒ + _ ← FutureExt.ftraverse(exMemberIds.toSeq) { userId ⇒ seqUpdExt.broadcastPeopleUpdate( - userIds = newState.memberIds - userId, + userIds = exMemberIds - userId, update = UpdateGroupUserLeaveObsolete(groupId, userId, dateMillis, randomId) ) } @@ -358,15 +365,21 @@ private[group] trait AdminCommandHandlers extends GroupsImplicits { /////////////////////////// // Groups V2 API updates // /////////////////////////// + + // send all members update about group became empty(no members) _ ← Future.traverse(deleteGroupMembersUpdates) { update ⇒ - seqUpdExt.broadcastPeopleUpdate(newState.memberIds, update) + seqUpdExt.broadcastPeopleUpdate(exMemberIds, update) } - seqState ← seqUpdExt.broadcastClientUpdate( - cmd.clientUserId, - cmd.clientAuthId, - bcastUserIds = state.memberIds - cmd.clientUserId, + + // send all members except clientUserId `UpdateChatClear` + _ ← seqUpdExt.broadcastPeopleUpdate( + userIds = exMemberIds - cmd.clientUserId, update = UpdateChatClear(apiGroupPeer) ) + + // delete dialog from client user's dialog list + // history deletion happens inside + seqState ← dialogExt.delete(cmd.clientUserId, cmd.clientAuthId, apiGroupPeer.asModel) } yield seqState result pipeTo sender() diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala index 96b26079ce..3f2959e687 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala @@ -296,8 +296,6 @@ private[group] final case class GroupState( def withSnapshot(metadata: SnapshotMetadata, snapshot: Any): GroupState = this object permissions { - val GroupEmpty = 0L - val FullGroupEmpty = 0L /////////////////////////// // General permissions // diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/names/GlobalNamesStorage.scala b/actor-server/actor-core/src/main/scala/im/actor/server/names/GlobalNamesStorage.scala index dc766a5ef7..b46ee46684 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/names/GlobalNamesStorage.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/names/GlobalNamesStorage.scala @@ -90,6 +90,7 @@ final class GlobalNamesStorageKeyValueStorage(implicit system: ActorSystem) { * `oldGlobalName` = None, `newGlobalName` = Some("name") - insert new name * `oldGlobalName` = Some("oldName"), `newGlobalName` = Some("name") - update existing name * `oldGlobalName` = Some("oldName"), `newGlobalName` = None - delete existing name + * `oldGlobalName` = None, `newGlobalName` = None - does nothing */ def updateOrRemove(oldGlobalName: Option[String], newGlobalName: Option[String], owner: GlobalNameOwner): Future[Unit] = { val deleteFu = (oldGlobalName map delete) getOrElse FastFuture.successful(()) From 5079060179750744beb46953831640ad1f6904a9 Mon Sep 17 00:00:00 2001 From: rockjam Date: Fri, 22 Jul 2016 04:10:38 +0300 Subject: [PATCH 179/414] feat(server:groups): new permissions added --- .../im/actor/server/group/GroupState.scala | 52 ++++++++++++++++--- .../server/group/MemberCommandHandlers.scala | 8 ++- 2 files changed, 51 insertions(+), 9 deletions(-) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala index 3f2959e687..2d683a4c73 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala @@ -359,16 +359,30 @@ private[group] final case class GroupState( * 5 - canEditAdminSettings. Default is FALSE. * 6 - canViewAdmins. Default is FALSE. * 7 - canEditAdmins. Default is FALSE. + * + * NOT DOCUMENTED YET: + * 8 - canKickInvited. Default is FALSE. + * 9 - canKickAnyone. Default is FALSE. + * 10 - canEditForeign. Default is FALSE. + * 11 - canDeleteForeign. Default is FALSE. */ def fullFor(userId: Int): Long = { - ((toInt(canEditInfo(userId)) << 0) + + ( + (toInt(canEditInfo(userId)) << 0) + (toInt(canViewMembers(userId)) << 1) + (toInt(canInviteMembers(userId)) << 2) + (toInt(canInviteViaLink(userId)) << 3) + (toInt(canCall(userId)) << 4) + (toInt(canEditAdminSettings(userId)) << 5) + (toInt(canViewAdmins(userId)) << 6) + - (toInt(canEditAdmins(userId)) << 7)).toLong + (toInt(canEditAdmins(userId)) << 7) + + + // NOT DOCUMENTED YET: + (toInt(canKickInvited(userId)) << 8) + + (toInt(canKickAnyone(userId)) << 9) + + (toInt(canEditForeign(userId)) << 10) + + (toInt(canDeleteForeign(userId)) << 11) + ).toLong } /** @@ -424,15 +438,37 @@ private[group] final case class GroupState( def canEditAdmins(clientUserId: Int): Boolean = isOwner(clientUserId) || isAdmin(clientUserId) - //////////////////////////// - // Internal permissions // - //////////////////////////// + /** + * In General group members can kick people they invited + * In Channel only owner and admins can kick invited people + */ + def canKickInvited(userId: Int): Boolean = + groupType match { + case General ⇒ isMember(userId) + case Channel ⇒ isAdmin(userId) || isOwner(userId) + } /** - * owner and admins can kick members + * Only owner and admins can kick anyone */ - def canKickMember(clientUserId: Int) = - isOwner(clientUserId) || isAdmin(clientUserId) + def canKickAnyone(userId: Int): Boolean = + isOwner(userId) || isAdmin(userId) + + /** + * Only owner and admins can edit foreign messages + */ + private def canEditForeign(userId: Int): Boolean = + isOwner(userId) || isAdmin(userId) + + /** + * Only owner and admins can delete foreign messages + */ + private def canDeleteForeign(userId: Int): Boolean = + isOwner(userId) || isAdmin(userId) + + //////////////////////////// + // Internal permissions // + //////////////////////////// // only owner can change short name def canEditShortName(clientUserId: Int): Boolean = isOwner(clientUserId) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/MemberCommandHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/MemberCommandHandlers.scala index 3fd617a513..efbd494dea 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/MemberCommandHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/MemberCommandHandlers.scala @@ -459,7 +459,13 @@ private[group] trait MemberCommandHandlers extends GroupsImplicits { } protected def kick(cmd: Kick): Unit = { - if (!state.permissions.canKickMember(cmd.kickerUserId)) { + val canKick = + state.permissions.canKickAnyone(cmd.kickerUserId) || + ( + state.permissions.canKickInvited(cmd.kickerUserId) && + state.members.get(cmd.kickedUserId).exists(_.inviterUserId == cmd.kickerUserId) // user we kick invited by kicker + ) + if (!canKick) { sender() ! noPermission } else if (state.nonMember(cmd.kickedUserId)) { sender() ! notMember From b8d7f7426873323943052d1244d208c8fbf65860 Mon Sep 17 00:00:00 2001 From: rockjam Date: Fri, 22 Jul 2016 16:18:09 +0300 Subject: [PATCH 180/414] fix(server:groups): group deletion updates --- .../src/main/scala/im/actor/server/dialog/DialogRoot.scala | 2 -- .../scala/im/actor/server/group/AdminCommandHandlers.scala | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogRoot.scala b/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogRoot.scala index eaae17e4e8..090a6d0553 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogRoot.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogRoot.scala @@ -270,7 +270,6 @@ private class DialogRoot(val userId: Int, extensions: Seq[ApiExtension]) } private def sendChatGroupsChanged(authId: Long): Future[SeqState] = { - val pushRules = if (authId == 0L) PushRules() else PushRules().withExcludeAuthIds(Seq(authId)) for { groups ← DialogExtension(system).fetchApiGroupedDialogs(userId) update = UpdateChatGroupsChanged(groups) @@ -278,7 +277,6 @@ private class DialogRoot(val userId: Int, extensions: Seq[ApiExtension]) userId, authId, update, - pushRules, reduceKey = Some(s"dialogschanged_${userId}") ) } yield seqState diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/AdminCommandHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/AdminCommandHandlers.scala index 8254b34115..5f750cbf6a 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/AdminCommandHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/AdminCommandHandlers.scala @@ -328,12 +328,12 @@ private[group] trait AdminCommandHandlers extends GroupsImplicits { val deleteGroupMembersUpdates: Vector[Update] = emptyPermissions ++ Vector( UpdateGroupMemberChanged(groupId, isMember = false), + UpdateGroupDeleted(groupId), // if channel, or group is big enough if (exGroupType.isChannel) UpdateGroupMembersCountChanged(groupId, membersCount = 0) else - UpdateGroupMembersUpdated(groupId, members = Vector.empty), - UpdateGroupDeleted(groupId) + UpdateGroupMembersUpdated(groupId, members = Vector.empty) ) //TODO: remove deprecated. GroupInviteTokenRepo don't have replacement yet. From e2cc1755290c8fa542c81d52fb376bd8c593ffe3 Mon Sep 17 00:00:00 2001 From: rockjam Date: Fri, 22 Jul 2016 16:23:36 +0300 Subject: [PATCH 181/414] fix(server): reorder group delete updates --- .../actor/server/group/AdminCommandHandlers.scala | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/AdminCommandHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/AdminCommandHandlers.scala index 5f750cbf6a..c96ef7e854 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/AdminCommandHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/AdminCommandHandlers.scala @@ -320,20 +320,17 @@ private[group] trait AdminCommandHandlers extends GroupsImplicits { val randomId = ACLUtils.randomLong() val ZeroPermissions = 0L - val emptyPermissions = Vector( - UpdateGroupPermissionsChanged(groupId, ZeroPermissions), - UpdateGroupFullPermissionsChanged(groupId, ZeroPermissions) - ) - - val deleteGroupMembersUpdates: Vector[Update] = emptyPermissions ++ + val deleteGroupMembersUpdates: Vector[Update] = Vector( UpdateGroupMemberChanged(groupId, isMember = false), - UpdateGroupDeleted(groupId), // if channel, or group is big enough - if (exGroupType.isChannel) + (if (exGroupType.isChannel) UpdateGroupMembersCountChanged(groupId, membersCount = 0) else - UpdateGroupMembersUpdated(groupId, members = Vector.empty) + UpdateGroupMembersUpdated(groupId, members = Vector.empty)), + UpdateGroupPermissionsChanged(groupId, ZeroPermissions), + UpdateGroupFullPermissionsChanged(groupId, ZeroPermissions), + UpdateGroupDeleted(groupId) ) //TODO: remove deprecated. GroupInviteTokenRepo don't have replacement yet. From 0aefce2d61dff423697c89b9b387cd40a6c0f8a6 Mon Sep 17 00:00:00 2001 From: rockjam Date: Fri, 22 Jul 2016 16:50:23 +0300 Subject: [PATCH 182/414] fix(server:groups): push empty members list on group delete for old API --- .../im/actor/server/group/AdminCommandHandlers.scala | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/AdminCommandHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/AdminCommandHandlers.scala index c96ef7e854..1dcd4b9d6a 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/AdminCommandHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/AdminCommandHandlers.scala @@ -351,13 +351,11 @@ private[group] trait AdminCommandHandlers extends GroupsImplicits { // Groups V1 API updates // /////////////////////////// - // push all members updates about other members left group - _ ← FutureExt.ftraverse(exMemberIds.toSeq) { userId ⇒ - seqUpdExt.broadcastPeopleUpdate( - userIds = exMemberIds - userId, - update = UpdateGroupUserLeaveObsolete(groupId, userId, dateMillis, randomId) - ) - } + // push all members updates about group members became empty + _ ← seqUpdExt.broadcastPeopleUpdate( + userIds = exMemberIds, + update = UpdateGroupMembersUpdateObsolete(groupId, members = Vector.empty) + ) /////////////////////////// // Groups V2 API updates // From 8b4a96028cd28d463a8c511b31b58bfbc3e6ed38 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Fri, 22 Jul 2016 16:06:44 +0300 Subject: [PATCH 183/414] chore(server): update actor.json --- .../actor-core/src/main/actor-api/actor.json | 149 ++++++++++++++++-- 1 file changed, 140 insertions(+), 9 deletions(-) diff --git a/actor-server/actor-core/src/main/actor-api/actor.json b/actor-server/actor-core/src/main/actor-api/actor.json index d707e0a5ed..bca140d112 100644 --- a/actor-server/actor-core/src/main/actor-api/actor.json +++ b/actor-server/actor-core/src/main/actor-api/actor.json @@ -7876,6 +7876,86 @@ ] } }, + { + "type": "enum", + "content": { + "name": "GroupPermissions", + "values": [ + { + "name": "SEND_MESSAGE", + "id": 1 + }, + { + "name": "CLEAR", + "id": 2 + }, + { + "name": "LEAVE", + "id": 3 + }, + { + "name": "DELETE", + "id": 4 + } + ] + } + }, + { + "type": "enum", + "content": { + "name": "GroupFullPermissions", + "values": [ + { + "name": "EDIT_INFO", + "id": 1 + }, + { + "name": "VIEW_MEMBERS", + "id": 2 + }, + { + "name": "INVITE_MEMBERS", + "id": 3 + }, + { + "name": "INVITE_VIA_LINK", + "id": 4 + }, + { + "name": "CALL", + "id": 5 + }, + { + "name": "EDIT_ADMIN_SETTINGS", + "id": 6 + }, + { + "name": "VIEW_ADMINS", + "id": 7 + }, + { + "name": "EDIT_ADMINS", + "id": 8 + }, + { + "name": "KICK_INVITED", + "id": 9 + }, + { + "name": "KICK_ANYONE", + "id": 10 + }, + { + "name": "EDIT_FOREIGN", + "id": 11 + }, + { + "name": "DELETE_FOREIGN", + "id": 12 + } + ] + } + }, { "type": "struct", "content": { @@ -8098,7 +8178,8 @@ "childType": "bool" }, "id": 16, - "name": "isAdmin" + "name": "isAdmin", + "deprecated": "true" }, { "type": { @@ -8106,7 +8187,8 @@ "childType": "userId" }, "id": 8, - "name": "creatorUid" + "name": "creatorUid", + "deprecated": "true" }, { "type": { @@ -8117,7 +8199,8 @@ } }, "id": 9, - "name": "members" + "name": "members", + "deprecated": "true" }, { "type": { @@ -8125,7 +8208,8 @@ "childType": "date" }, "id": 10, - "name": "createDate" + "name": "createDate", + "deprecated": "true" }, { "type": { @@ -8133,7 +8217,8 @@ "childType": "string" }, "id": 17, - "name": "theme" + "name": "theme", + "deprecated": "true" }, { "type": { @@ -8141,7 +8226,8 @@ "childType": "string" }, "id": 18, - "name": "about" + "name": "about", + "deprecated": "true" } ] } @@ -8167,6 +8253,10 @@ "5 - canEditAdminSettings. Default is FALSE.", "6 - canViewAdmins. Default is FALSE.", "7 - canEditAdmins. Default is FALSE.", + "8 - canKickInvited. Default is FALSE.", + "9 - canKickAnyone. Default is FALSE.", + "10 - canEditForeign. Default is FALSE.", + "11 - canDeleteForeign. Default is FALSE.", "", { "type": "reference", @@ -8396,7 +8486,7 @@ "doc": [ { "type": "reference", - "argument": "members", + "argument": "users", "category": "full", "description": " Group members" }, @@ -8408,6 +8498,17 @@ } ], "attributes": [ + { + "type": { + "type": "list", + "childType": { + "type": "struct", + "childType": "Member" + } + }, + "id": 3, + "name": "members" + }, { "type": { "type": "list", @@ -8417,7 +8518,7 @@ } }, "id": 1, - "name": "members" + "name": "users" }, { "type": { @@ -9978,6 +10079,36 @@ ] } }, + { + "type": "rpc", + "content": { + "name": "LeaveAndDelete", + "header": 2721, + "response": { + "type": "reference", + "name": "Seq" + }, + "doc": [ + "Leave group and Delete Chat", + { + "type": "reference", + "argument": "groupPeer", + "category": "full", + "description": "Group peer" + } + ], + "attributes": [ + { + "type": { + "type": "struct", + "childType": "GroupOutPeer" + }, + "id": 1, + "name": "groupPeer" + } + ] + } + }, { "type": "rpc", "content": { @@ -21581,4 +21712,4 @@ ] } ] -} \ No newline at end of file +} From 790b1a2c0da0d985b418768fde22151c0a0434a7 Mon Sep 17 00:00:00 2001 From: rockjam Date: Fri, 22 Jul 2016 18:05:44 +0300 Subject: [PATCH 184/414] feat(server:groups): leaveAndDelete, change load members --- .../src/main/protobuf/groupV2.proto | 9 ++++++- .../im/actor/api/rpc/PeersImplicits.scala | 3 +++ .../actor/server/group/GroupOperations.scala | 9 ++++++- .../server/group/GroupQueryHandlers.scala | 18 +++++++++----- .../im/actor/server/group/GroupState.scala | 6 ++--- .../service/groups/GroupsServiceImpl.scala | 24 ++++++++++++++++--- 6 files changed, 54 insertions(+), 15 deletions(-) diff --git a/actor-server/actor-core/src/main/protobuf/groupV2.proto b/actor-server/actor-core/src/main/protobuf/groupV2.proto index 3d524d5339..d402520650 100644 --- a/actor-server/actor-core/src/main/protobuf/groupV2.proto +++ b/actor-server/actor-core/src/main/protobuf/groupV2.proto @@ -18,6 +18,13 @@ import "file.proto"; import "sequence.proto"; import "dialog.proto"; +message GroupMember { + int32 user_id = 1; + int32 inviter_user_id = 2; + int64 invited_at = 3; + bool is_admin = 4; +} + message GroupEnvelope { int32 group_id = 1; oneof command { @@ -306,7 +313,7 @@ message GroupQueries { } message LoadMembersResponse { - repeated int32 user_ids = 1; + repeated GroupMember members = 1; google.protobuf.BytesValue offset = 2;// should it be Option[Array[Byte]] } diff --git a/actor-server/actor-core/src/main/scala/im/actor/api/rpc/PeersImplicits.scala b/actor-server/actor-core/src/main/scala/im/actor/api/rpc/PeersImplicits.scala index d9d0e1b5ee..f8a868c348 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/api/rpc/PeersImplicits.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/api/rpc/PeersImplicits.scala @@ -24,6 +24,9 @@ trait PeersImplicits { lazy val asPeer: ApiPeer = ApiPeer(ApiPeerType.Group, groupOutPeer.groupId) + + lazy val asModel: Peer = + Peer(PeerType.Group, groupOutPeer.groupId) } implicit class ExtPeerModel(model: Peer) { diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupOperations.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupOperations.scala index fdbb22efc5..646e0cc15b 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupOperations.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupOperations.scala @@ -208,7 +208,14 @@ private[group] sealed trait Queries { def loadMembers(groupId: Int, clientUserId: Int, limit: Int, offset: Option[Array[Byte]]) = (viewRegion.ref ? GroupEnvelope(groupId) - .withLoadMembers(LoadMembers(clientUserId, limit, offset map ByteString.copyFrom))).mapTo[LoadMembersResponse] map (r ⇒ r.userIds → r.offset.map(_.toByteArray)) + .withLoadMembers(LoadMembers(clientUserId, limit, offset map ByteString.copyFrom))).mapTo[LoadMembersResponse] map { resp ⇒ + ( + resp.members map { m ⇒ + ApiMember(m.userId, m.inviterUserId, m.invitedAt, isAdmin = Some(m.isAdmin)) + }, + resp.offset.map(_.toByteArray) + ) + } def loadAdminSettings(groupId: Int, clientUserId: Int): Future[ApiAdminSettings] = { (viewRegion.ref ? diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala index ddfb122cdf..71fad35c88 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala @@ -45,23 +45,29 @@ trait GroupQueryHandlers { ) } - //TODO: rewrite to sort by online + name. Won't work like this - // we can subscribe group object to group onlines! When online comes, we reorder key-set. Use that key set as source. protected def loadMembers(clientUserId: Int, limit: Int, offsetBs: Option[ByteString]): Future[LoadMembersResponse] = { def load = { implicit val mat = ActorMaterializer() val offset = offsetBs map (_.toByteArray) map (Int32Value.parseFrom(_).value) getOrElse 0 for { - (userIds, nextOffset) ← Source(state.members.keySet) - .mapAsync(1)(userId ⇒ userExt.getName(userId, clientUserId) map (userId → _)) - .runFold(Vector.empty[(Int, String)])(_ :+ _) map { users ⇒ + (members, nextOffset) ← Source(state.members) + .mapAsync(1)(member ⇒ userExt.getName(member._1, clientUserId) map (member._2 → _)) + .runFold(Vector.empty[(Member, String)])(_ :+ _) map { users ⇒ val tail = users.sortBy(_._2).map(_._1).drop(offset) val nextOffset = if (tail.length > limit) Some(Int32Value(offset + limit).toByteArray) else None (tail.take(limit), nextOffset) } } yield LoadMembersResponse( - userIds = userIds, + members = members map { + case Member(userId, inviterUserId, invitedAt, isAdmin) ⇒ + GroupMember( + userId, + inviterUserId, + invitedAt.toEpochMilli, + isAdmin + ) + }, offset = nextOffset map ByteString.copyFrom ) } diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala index 2d683a4c73..5a87046151 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala @@ -310,6 +310,7 @@ private[group] final case class GroupState( * 2 - canLeave. Default is FALSE. * 3 - canDelete. Default is FALSE. */ + // TODO: add ApiGroupFullPermissions def groupFor(userId: Int): Long = { ((toInt(canSendMessage(userId)) << 0) + (toInt(canClear(userId)) << 1) + @@ -359,13 +360,12 @@ private[group] final case class GroupState( * 5 - canEditAdminSettings. Default is FALSE. * 6 - canViewAdmins. Default is FALSE. * 7 - canEditAdmins. Default is FALSE. - * - * NOT DOCUMENTED YET: * 8 - canKickInvited. Default is FALSE. * 9 - canKickAnyone. Default is FALSE. * 10 - canEditForeign. Default is FALSE. * 11 - canDeleteForeign. Default is FALSE. */ + // TODO: add ApiGroupFullPermissions def fullFor(userId: Int): Long = { ( (toInt(canEditInfo(userId)) << 0) + @@ -376,8 +376,6 @@ private[group] final case class GroupState( (toInt(canEditAdminSettings(userId)) << 5) + (toInt(canViewAdmins(userId)) << 6) + (toInt(canEditAdmins(userId)) << 7) + - - // NOT DOCUMENTED YET: (toInt(canKickInvited(userId)) << 8) + (toInt(canKickAnyone(userId)) << 9) + (toInt(canEditForeign(userId)) << 10) + diff --git a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupsServiceImpl.scala b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupsServiceImpl.scala index 7e03397d99..88cbae7636 100644 --- a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupsServiceImpl.scala +++ b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupsServiceImpl.scala @@ -16,6 +16,7 @@ import im.actor.api.rpc.users.ApiUser import im.actor.concurrent.FutureExt import im.actor.server.acl.ACLUtils import im.actor.server.db.DbExtension +import im.actor.server.dialog.DialogExtension import im.actor.server.file.{ FileErrors, ImageUtils } import im.actor.server.group._ import im.actor.server.model.GroupInviteToken @@ -47,6 +48,7 @@ final class GroupsServiceImpl(groupInviteConfig: GroupInviteConfig)(implicit act private val userExt = UserExtension(actorSystem) private val groupPresenceExt = GroupPresenceExtension(actorSystem) private val globalNamesStorage = new GlobalNamesStorageKeyValueStorage + private val dialogExt = DialogExtension(actorSystem) /** * Loading Full Groups @@ -128,9 +130,12 @@ final class GroupsServiceImpl(groupInviteConfig: GroupInviteConfig)(implicit act authorized(clientData) { implicit client ⇒ withGroupOutPeer(groupPeer) { for { - (userIds, nextOffset) ← groupExt.loadMembers(groupPeer.groupId, client.userId, limit, next) - members ← FutureExt.ftraverse(userIds)(userExt.getApiStruct(_, client.userId, client.authId)) - } yield Ok(ResponseLoadMembers(members.toVector map (u ⇒ ApiUserOutPeer(u.id, u.accessHash)), nextOffset)) + (members, nextOffset) ← groupExt.loadMembers(groupPeer.groupId, client.userId, limit, next) + membersAndPeers ← FutureExt.ftraverse(members) { member ⇒ + userExt.getAccessHash(member.userId, client.authId) map (hash ⇒ member → ApiUserOutPeer(member.userId, hash)) + } + (members, peers) = membersAndPeers.unzip + } yield Ok(ResponseLoadMembers(peers.toVector, nextOffset, members.toVector)) } } } @@ -248,6 +253,19 @@ final class GroupsServiceImpl(groupInviteConfig: GroupInviteConfig)(implicit act } } + override def doHandleLeaveAndDelete(groupPeer: ApiGroupOutPeer, clientData: ClientData): Future[HandlerResult[ResponseSeq]] = + authorized(clientData) { implicit client ⇒ + withGroupOutPeer(groupPeer) { + for { + _ ← groupExt.leaveGroup(groupPeer.groupId, ACLUtils.randomLong()) + SeqState(seq, state) ← dialogExt.delete(client.userId, client.authId, groupPeer.asModel) + } yield { + groupPresenceExt.notifyGroupUserRemoved(groupPeer.groupId, client.userId) + Ok(ResponseSeq(seq, state.toByteArray)) + } + } + } + override def doHandleCreateGroup( randomId: Long, title: String, From 57737c98062736e6a81f500421aff97683967604 Mon Sep 17 00:00:00 2001 From: rockjam Date: Fri, 22 Jul 2016 18:36:01 +0300 Subject: [PATCH 185/414] fix(server:search): lowercase nick search queries --- .../main/scala/im/actor/server/names/GlobalNamesStorage.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/names/GlobalNamesStorage.scala b/actor-server/actor-core/src/main/scala/im/actor/server/names/GlobalNamesStorage.scala index b46ee46684..a78b47e909 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/names/GlobalNamesStorage.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/names/GlobalNamesStorage.scala @@ -39,7 +39,7 @@ final class GlobalNamesStorageKeyValueStorage(implicit system: ActorSystem) { * Looks only in GlobalNamesStorage */ def groupIdsByPrefix(namePrefix: String): Future[IndexedSeq[(Int, String)]] = { - conn.run(GlobalNamesStorage.getByPrefix(namePrefix)) map { searchResults ⇒ + conn.run(GlobalNamesStorage.getByPrefix(normalized(namePrefix))) map { searchResults ⇒ searchResults flatMap { case (fullName, bytes) ⇒ Some(GlobalNameOwner.parseFrom(bytes)) filter (_.ownerType.isGroup) map (o ⇒ o.ownerId → fullName) @@ -52,7 +52,7 @@ final class GlobalNamesStorageKeyValueStorage(implicit system: ActorSystem) { * Looks in both GlobalNamesStorage and UserRepo(compatibility mode) */ def userIdsByPrefix(namePrefix: String): Future[IndexedSeq[(Int, String)]] = { - val kvSearch = conn.run(GlobalNamesStorage.getByPrefix(namePrefix)) map { searchResults ⇒ + val kvSearch = conn.run(GlobalNamesStorage.getByPrefix(normalized(namePrefix))) map { searchResults ⇒ searchResults flatMap { case (fullName, bytes) ⇒ Some(GlobalNameOwner.parseFrom(bytes)) filter (_.ownerType.isUser) map (o ⇒ o.ownerId → fullName) From 7f097774f7b5dccdbd55819e750f470447b2734a Mon Sep 17 00:00:00 2001 From: rockjam Date: Fri, 22 Jul 2016 19:31:57 +0300 Subject: [PATCH 186/414] chore(server): update actor.json --- .../actor-core/src/main/actor-api/actor.json | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/actor-server/actor-core/src/main/actor-api/actor.json b/actor-server/actor-core/src/main/actor-api/actor.json index bca140d112..35fd7b3469 100644 --- a/actor-server/actor-core/src/main/actor-api/actor.json +++ b/actor-server/actor-core/src/main/actor-api/actor.json @@ -6601,6 +6601,32 @@ ] } }, + { + "type": "update", + "content": { + "name": "ChatDropCache", + "header": 2690, + "doc": [ + "Update about cache drop", + { + "type": "reference", + "argument": "peer", + "category": "full", + "description": " Destination peer" + } + ], + "attributes": [ + { + "type": { + "type": "struct", + "childType": "Peer" + }, + "id": 1, + "name": "peer" + } + ] + } + }, { "type": "update", "content": { @@ -7896,6 +7922,14 @@ { "name": "DELETE", "id": 4 + }, + { + "name": "JOIN", + "id": 5 + }, + { + "name": "VIEW_INFO", + "id": 6 } ] } @@ -7975,6 +8009,8 @@ "1 - canClear. Default is FALSE.", "2 - canLeave. Default is FALSE.", "3 - canDelete. Default is FALSE.", + "4 - canJoin. Default is FALSE.", + "5 - canViewInfo. Default is FALSE.", "", { "type": "reference", From 521ff24a5b97145c4f221322db169b54a20c8e48 Mon Sep 17 00:00:00 2001 From: rockjam Date: Fri, 22 Jul 2016 19:34:05 +0300 Subject: [PATCH 187/414] =?UTF-8?q?feat(server:groups):=20=20=20=20?= =?UTF-8?q?=E2=80=A2=20add=20canJoin,=20canViewInfo=20permisssions=20to=20?= =?UTF-8?q?group;=20=20=20=20=E2=80=A2=20send=20ChatDropCache=20on=20group?= =?UTF-8?q?=20join?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../im/actor/server/group/GroupState.scala | 13 +++++++++++- .../server/group/MemberCommandHandlers.scala | 20 +++++++++++++------ 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala index 5a87046151..b600c77d69 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala @@ -309,13 +309,17 @@ private[group] final case class GroupState( * 1 - canClear. Default is FALSE. * 2 - canLeave. Default is FALSE. * 3 - canDelete. Default is FALSE. + * 4 - canJoin. Default is FALSE. + * 5 - canViewInfo. Default is FALSE. */ // TODO: add ApiGroupFullPermissions def groupFor(userId: Int): Long = { ((toInt(canSendMessage(userId)) << 0) + (toInt(canClear(userId)) << 1) + (toInt(canLeave(userId)) << 2) + - (toInt(canDelete(userId)) << 3)).toLong + (toInt(canDelete(userId)) << 3) + + (toInt(canJoin(userId)) << 4) + + (toInt(canViewInfo(userId)) << 5)).toLong } /** @@ -344,6 +348,13 @@ private[group] final case class GroupState( // only owner can delete group def canDelete(clientUserId: Int): Boolean = isOwner(clientUserId) + // anyone can join in group with shared history + def canJoin(clientUserId: Int): Boolean = isHistoryShared + + // if history shared - anyone can view info + // only members can view info in private groups + def canViewInfo(clientUserId: Int): Boolean = isHistoryShared || isMember(clientUserId) + //////////////////////////// // Full group permissions // //////////////////////////// diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/MemberCommandHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/MemberCommandHandlers.scala index efbd494dea..5c82a2a377 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/MemberCommandHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/MemberCommandHandlers.scala @@ -6,7 +6,7 @@ import akka.actor.Status import akka.pattern.pipe import im.actor.api.rpc.Update import im.actor.api.rpc.groups._ -import im.actor.api.rpc.messaging.{ ApiServiceMessage, UpdateMessage } +import im.actor.api.rpc.messaging.{ ApiServiceMessage, UpdateChatDropCache, UpdateMessage } import im.actor.concurrent.FutureExt import im.actor.server.acl.ACLUtils import im.actor.server.group.GroupCommands.{ Invite, Join, Kick, Leave } @@ -39,8 +39,12 @@ private[group] trait MemberCommandHandlers extends GroupsImplicits { val memberIds = newState.memberIds val apiMembers = newState.members.values.map(_.asStruct).toVector - // if user ever been in this group - we should push these updates, - val inviteeUpdatesNew: Vector[Update] = refreshGroupUpdates(newState, cmd.inviteeUserId) + // if user ever been in this group - we should push these updates + // TODO: unify isHistoryShared usage + val inviteeUpdatesNew: Vector[Update] = { + val optDrop = if(newState.isHistoryShared) Some(UpdateChatDropCache(apiGroupPeer)) else None + optDrop ++: refreshGroupUpdates(newState, cmd.inviteeUserId) + } val membersUpdateNew: Update = if (newState.groupType.isChannel) // if channel, or group is big enough @@ -219,9 +223,12 @@ private[group] trait MemberCommandHandlers extends GroupsImplicits { // that means we need to push all group-info related updates // // If user was invited to group by other member - we don't need to push group updates, - // cause they we pushed already on invite step - val joiningUserUpdatesNew: Vector[Update] = - if (wasInvited) Vector.empty[Update] else refreshGroupUpdates(newState, cmd.joiningUserId) + // cause they were pushed already on invite step + // TODO: unify isHistoryShared usage + val joiningUserUpdatesNew: Vector[Update] = { + val optDrop = if(newState.isHistoryShared) Some(UpdateChatDropCache(apiGroupPeer)) else None + optDrop ++: (if (wasInvited) Vector.empty[Update] else refreshGroupUpdates(newState, cmd.joiningUserId)) + } val membersUpdateNew: Update = if (newState.groupType.isChannel) // if channel, or group is big enough @@ -594,6 +601,7 @@ private[group] trait MemberCommandHandlers extends GroupsImplicits { // Updates that will be sent to user, when he enters group. // Helps clients that have this group to refresh it's data. private def refreshGroupUpdates(newState: GroupState, userId: Int): Vector[Update] = Vector( + UpdateChatDropCache(apiGroupPeer), UpdateGroupMemberChanged(groupId, isMember = true), UpdateGroupAboutChanged(groupId, newState.about), UpdateGroupAvatarChanged(groupId, newState.avatar), From a2780905820406d79b7a417aca991091f461ee59 Mon Sep 17 00:00:00 2001 From: rockjam Date: Fri, 22 Jul 2016 21:04:18 +0300 Subject: [PATCH 188/414] fix(server:groups): clear and delete cleanup --- .../server/group/AdminCommandHandlers.scala | 14 ++++++++++++++ .../server/group/MemberCommandHandlers.scala | 4 ++-- .../server/persist/HistoryMessageRepo.scala | 1 - .../rpc/service/messaging/HistoryHandlers.scala | 17 +++++++++-------- 4 files changed, 25 insertions(+), 11 deletions(-) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/AdminCommandHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/AdminCommandHandlers.scala index 1dcd4b9d6a..df37b3b8e4 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/AdminCommandHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/AdminCommandHandlers.scala @@ -11,6 +11,7 @@ import im.actor.api.rpc.messaging.UpdateChatClear import im.actor.concurrent.FutureExt import im.actor.server.CommonErrors import im.actor.server.acl.ACLUtils +import im.actor.server.dialog.HistoryUtils import im.actor.server.group.GroupCommands.{ DeleteGroup, DismissUserAdmin, MakeHistoryShared, MakeUserAdmin, RevokeIntegrationToken, RevokeIntegrationTokenAck, TransferOwnership, UpdateAdminSettings, UpdateAdminSettingsAck } import im.actor.server.group.GroupErrors.{ NotAMember, NotAdmin, UserAlreadyAdmin, UserAlreadyNotAdmin } import im.actor.server.group.GroupEvents.{ AdminSettingsUpdated, AdminStatusChanged, GroupDeleted, HistoryBecameShared, IntegrationTokenRevoked, OwnerChanged } @@ -312,6 +313,8 @@ private[group] trait AdminCommandHandlers extends GroupsImplicits { val exMemberIds = state.memberIds val exGlobalName = state.shortName val exGroupType = state.groupType + val exHistoryShared = state.isHistoryShared + val peer = apiGroupPeer.asModel persist(GroupDeleted(Instant.now, cmd.clientUserId)) { evt ⇒ commit(evt) @@ -347,6 +350,17 @@ private[group] trait AdminCommandHandlers extends GroupsImplicits { // release global name of group _ ← globalNamesStorage.updateOrRemove(exGlobalName, newGlobalName = None, GlobalNameOwner(OwnerType.Group, groupId)) + // explicitly delete group history. + // TODO: move to utility method + _ ← if (exHistoryShared) { + db.run(HistoryMessageRepo.deleteAll(HistoryUtils.SharedUserId, peer)) + } else { + // for client user we delete history separately + FutureExt.ftraverse((exMemberIds - cmd.clientUserId).toSeq) { userId ⇒ + db.run(HistoryMessageRepo.deleteAll(userId, peer)) + } + } + /////////////////////////// // Groups V1 API updates // /////////////////////////// diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/MemberCommandHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/MemberCommandHandlers.scala index 5c82a2a377..06b19c0976 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/MemberCommandHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/MemberCommandHandlers.scala @@ -42,7 +42,7 @@ private[group] trait MemberCommandHandlers extends GroupsImplicits { // if user ever been in this group - we should push these updates // TODO: unify isHistoryShared usage val inviteeUpdatesNew: Vector[Update] = { - val optDrop = if(newState.isHistoryShared) Some(UpdateChatDropCache(apiGroupPeer)) else None + val optDrop = if (newState.isHistoryShared) Some(UpdateChatDropCache(apiGroupPeer)) else None optDrop ++: refreshGroupUpdates(newState, cmd.inviteeUserId) } @@ -226,7 +226,7 @@ private[group] trait MemberCommandHandlers extends GroupsImplicits { // cause they were pushed already on invite step // TODO: unify isHistoryShared usage val joiningUserUpdatesNew: Vector[Update] = { - val optDrop = if(newState.isHistoryShared) Some(UpdateChatDropCache(apiGroupPeer)) else None + val optDrop = if (newState.isHistoryShared) Some(UpdateChatDropCache(apiGroupPeer)) else None optDrop ++: (if (wasInvited) Vector.empty[Update] else refreshGroupUpdates(newState, cmd.joiningUserId)) } diff --git a/actor-server/actor-persist/src/main/scala/im/actor/server/persist/HistoryMessageRepo.scala b/actor-server/actor-persist/src/main/scala/im/actor/server/persist/HistoryMessageRepo.scala index fc80a2712b..a7d03e9cd1 100644 --- a/actor-server/actor-persist/src/main/scala/im/actor/server/persist/HistoryMessageRepo.scala +++ b/actor-server/actor-persist/src/main/scala/im/actor/server/persist/HistoryMessageRepo.scala @@ -214,7 +214,6 @@ object HistoryMessageRepo { .result def deleteAll(userId: Int, peer: Peer): FixedSqlAction[Int, NoStream, Write] = { - require(userId != SharedUserId, "Can't delete messages for shared user") notDeletedMessages .filter(m ⇒ m.userId === userId && m.peerType === peer.typ.value && m.peerId === peer.id) .map(_.deletedAt) diff --git a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/messaging/HistoryHandlers.scala b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/messaging/HistoryHandlers.scala index 4223d3ed4d..fb55eed14d 100644 --- a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/messaging/HistoryHandlers.scala +++ b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/messaging/HistoryHandlers.scala @@ -52,24 +52,25 @@ trait HistoryHandlers { case ApiPeerType.Private | ApiPeerType.EncryptedPrivate ⇒ FastFuture.successful(true) case ApiPeerType.Group ⇒ - groupExt.isHistoryShared(peer.id) map (!_) + groupExt.isHistoryShared(peer.id) map (isShared ⇒ !isShared) } - for { - canDelete ← canClearChat - SeqState(seq, state) ← if (canDelete) { + canClearChat flatMap { canClear ⇒ + if (canClear) { for { _ ← db.run(HistoryMessageRepo.deleteAll(client.userId, peer.asModel)) - seqState ← seqUpdExt.deliverClientUpdate( + SeqState(seq, state) ← seqUpdExt.deliverClientUpdate( client.userId, client.authId, update = UpdateChatClear(peer.asPeer) ) - } yield seqState + } yield Ok(ResponseSeq(seq, state.toByteArray)) } else { - FastFuture.successful(Error(CommonRpcErrors.forbidden("Can't clear chat with shared history"))) + FastFuture.successful[HandlerResult[ResponseSeq]]( + Error(CommonRpcErrors.forbidden("Can't clear chat with shared history")) + ) } - } yield Ok(ResponseSeq(seq, state.toByteArray)) + } } override def doHandleDeleteChat(peer: ApiOutPeer, clientData: ClientData): Future[HandlerResult[ResponseSeq]] = { From 5d35d4f54c5994758003672b1628b9fcc16c48b4 Mon Sep 17 00:00:00 2001 From: rockjam Date: Sun, 24 Jul 2016 22:24:14 +0300 Subject: [PATCH 189/414] fix(server:dialogs): return UpdateChatDelete on chat delete --- .../src/main/scala/im/actor/server/dialog/DialogRoot.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogRoot.scala b/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogRoot.scala index 090a6d0553..a177f55057 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogRoot.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogRoot.scala @@ -219,8 +219,8 @@ private class DialogRoot(val userId: Int, extensions: Seq[ApiExtension]) commit(e) (for { _ ← db.run(HistoryMessageRepo.deleteAll(userId, peer)) - _ ← seqUpdExt.deliverUserUpdate(userId, UpdateChatDelete(peer.asStruct)) - seqState ← sendChatGroupsChanged(clientAuthId) + seqState ← seqUpdExt.deliverClientUpdate(userId, clientAuthId, update = UpdateChatDelete(peer.asStruct)) + _ ← sendChatGroupsChanged(0L) // _ = thatDialog ! PoisonPill // kill that dialog would be good } yield seqState) pipeTo sender() } From 4e8f50ddc007908dd55e1515f16f8d636cba2693 Mon Sep 17 00:00:00 2001 From: rockjam Date: Sun, 24 Jul 2016 22:59:05 +0300 Subject: [PATCH 190/414] chore(server): update actor.json --- .../actor-core/src/main/actor-api/actor.json | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/actor-server/actor-core/src/main/actor-api/actor.json b/actor-server/actor-core/src/main/actor-api/actor.json index 35fd7b3469..5e9fdbab71 100644 --- a/actor-server/actor-core/src/main/actor-api/actor.json +++ b/actor-server/actor-core/src/main/actor-api/actor.json @@ -10220,6 +10220,36 @@ ] } }, + { + "type": "rpc", + "content": { + "name": "JoinGroupByPeer", + "header": 2722, + "response": { + "type": "reference", + "name": "Seq" + }, + "doc": [ + "Join group by peer", + { + "type": "reference", + "argument": "groupPeer", + "category": "full", + "description": "Group's peer" + } + ], + "attributes": [ + { + "type": { + "type": "struct", + "childType": "GroupOutPeer" + }, + "id": 1, + "name": "groupPeer" + } + ] + } + }, { "type": "comment", "content": "Administration" From 141a84301b2344c32f940fc100df58c3138fc94e Mon Sep 17 00:00:00 2001 From: rockjam Date: Sun, 24 Jul 2016 22:59:35 +0300 Subject: [PATCH 191/414] feat(server:groups): implement join group by peer method --- .../rpc/service/groups/GroupRpcErrors.scala | 1 + .../service/groups/GroupsServiceImpl.scala | 22 +++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupRpcErrors.scala b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupRpcErrors.scala index ecf61be123..e668f74df1 100644 --- a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupRpcErrors.scala +++ b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupRpcErrors.scala @@ -19,6 +19,7 @@ object GroupRpcErrors { val InvalidInviteGroup = RpcError(403, "INVALID_INVITE_GROUP", "Invalid group name provided!", false, None) val GroupNotPublic = RpcError(400, "GROUP_IS_NOT_PUBLIC", "The group is not public.", false, None) val CantLeaveGroup = RpcError(403, "CANT_LEAVE_GROUP", "You can't leave this group!", false, None) + val CantJoinGroup = RpcError(403, "CANT_JOIN_GROUP", "You can't join this group!", false, None) val InvalidShortName = RpcError(400, "GROUP_SHORT_NAME_INVALID", "Invalid group short name. Valid short name should contain from 5 to 32 characters, and may consist of latin characters, numbers and underscores", false, None) val ShortNameTaken = RpcError(400, "GROUP_SHORT_NAME_TAKEN", "This short name already belongs to other user or group, we are sorry!", false, None) diff --git a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupsServiceImpl.scala b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupsServiceImpl.scala index 88cbae7636..c6876f852a 100644 --- a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupsServiceImpl.scala +++ b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupsServiceImpl.scala @@ -413,6 +413,28 @@ final class GroupsServiceImpl(groupInviteConfig: GroupInviteConfig)(implicit act action.value } + override def doHandleJoinGroupByPeer(groupPeer: ApiGroupOutPeer, clientData: ClientData): Future[HandlerResult[ResponseSeq]] = + authorized(clientData) { implicit client ⇒ + withGroupOutPeer(groupPeer) { + val action = for { + apiGroup ← fromFuture(groupExt.getApiStruct(groupPeer.groupId, client.userId)) + _ ← fromBoolean(GroupRpcErrors.CantJoinGroup)(canJoin(apiGroup.permissions)) + joinResp ← fromFuture(groupExt.joinGroup( + groupId = groupPeer.groupId, + joiningUserId = client.userId, + joiningUserAuthId = client.authId, + invitingUserId = None + )) + SeqStateDate(seq, state, _) = joinResp._1 + } yield ResponseSeq(seq, state.toByteArray) + + action.value + } + } + + private def canJoin(permissions: Option[Long]) = + permissions exists (p ⇒ (p & (1 << 4)) != 0) // TODO: make wrapper around permissions + override def doHandleRevokeInviteUrl(groupPeer: ApiGroupOutPeer, clientData: ClientData): Future[HandlerResult[ResponseInviteUrl]] = authorized(clientData) { implicit client ⇒ withGroupOutPeer(groupPeer) { From 11d199fdc8d3246243d5d68aaf5ac8babac3d16b Mon Sep 17 00:00:00 2001 From: rockjam Date: Mon, 25 Jul 2016 16:43:08 +0300 Subject: [PATCH 192/414] fix(server:contacts): clean unregistered contacts API --- .../persist/contact/UnregisteredEmailContactRepo.scala | 9 +++------ .../persist/contact/UnregisteredPhoneContactRepo.scala | 4 ++-- .../im/actor/server/user/ContactRegisteredSpec.scala | 4 ++-- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/actor-server/actor-persist/src/main/scala/im/actor/server/persist/contact/UnregisteredEmailContactRepo.scala b/actor-server/actor-persist/src/main/scala/im/actor/server/persist/contact/UnregisteredEmailContactRepo.scala index 016b81783d..1853f58ab6 100644 --- a/actor-server/actor-persist/src/main/scala/im/actor/server/persist/contact/UnregisteredEmailContactRepo.scala +++ b/actor-server/actor-persist/src/main/scala/im/actor/server/persist/contact/UnregisteredEmailContactRepo.scala @@ -12,14 +12,11 @@ final class UnregisteredEmailContactTable(tag: Tag) extends UnregisteredContactB } object UnregisteredEmailContactRepo { - val emailContacts = TableQuery[UnregisteredEmailContactTable] + private val emailContacts = TableQuery[UnregisteredEmailContactTable] - def create(email: String, ownerUserId: Int, name: Option[String]) = + private def create(email: String, ownerUserId: Int, name: Option[String]) = emailContacts += UnregisteredEmailContact(email, ownerUserId, name) - def create(contacts: Seq[UnregisteredEmailContact]) = - emailContacts ++= contacts - def createIfNotExists(email: String, ownerUserId: Int, name: Option[String]) = { create(email, ownerUserId, name).asTry } @@ -29,4 +26,4 @@ object UnregisteredEmailContactRepo { def deleteAll(email: String) = emailContacts.filter(_.email === email).delete -} \ No newline at end of file +} diff --git a/actor-server/actor-persist/src/main/scala/im/actor/server/persist/contact/UnregisteredPhoneContactRepo.scala b/actor-server/actor-persist/src/main/scala/im/actor/server/persist/contact/UnregisteredPhoneContactRepo.scala index c1a20bfbb1..d4d52c3e05 100644 --- a/actor-server/actor-persist/src/main/scala/im/actor/server/persist/contact/UnregisteredPhoneContactRepo.scala +++ b/actor-server/actor-persist/src/main/scala/im/actor/server/persist/contact/UnregisteredPhoneContactRepo.scala @@ -12,9 +12,9 @@ final class UnregisteredPhoneContactTable(tag: Tag) extends UnregisteredContactB } object UnregisteredPhoneContactRepo { - val phoneContacts = TableQuery[UnregisteredPhoneContactTable] + private val phoneContacts = TableQuery[UnregisteredPhoneContactTable] - def create(phoneNumber: Long, ownerUserId: Int, name: Option[String]) = + private def create(phoneNumber: Long, ownerUserId: Int, name: Option[String]) = phoneContacts += UnregisteredPhoneContact(phoneNumber, ownerUserId, name) def createIfNotExists(phoneNumber: Long, ownerUserId: Int, name: Option[String]) = { diff --git a/actor-server/actor-tests/src/test/scala/im/actor/server/user/ContactRegisteredSpec.scala b/actor-server/actor-tests/src/test/scala/im/actor/server/user/ContactRegisteredSpec.scala index c84b3b4438..ba40d6086d 100644 --- a/actor-server/actor-tests/src/test/scala/im/actor/server/user/ContactRegisteredSpec.scala +++ b/actor-server/actor-tests/src/test/scala/im/actor/server/user/ContactRegisteredSpec.scala @@ -87,7 +87,7 @@ final class ContactRegisteredSpec extends BaseAppSuite with ImplicitAuthService val (alice, aliceAuthId, aliceAuthSid, _) = createUser() val aliceClientData = ClientData(aliceAuthId, 1, Some(AuthData(alice.id, aliceAuthSid, 42))) - whenReady(db.run(UnregisteredEmailContactRepo.create("test@acme.com", alice.id, None)))(identity) + whenReady(db.run(UnregisteredEmailContactRepo.createIfNotExists("test@acme.com", alice.id, None)))(identity) val (bob, bobAuthId, bobAuthSid, _) = createUser() val bobClientData = ClientData(bobAuthId, 1, Some(AuthData(bob.id, bobAuthSid, 42))) @@ -96,4 +96,4 @@ final class ContactRegisteredSpec extends BaseAppSuite with ImplicitAuthService (aliceClientData, bobClientData) } -} \ No newline at end of file +} From e1e8a94f79eaa9db85c9aa195e8dba41727d8860 Mon Sep 17 00:00:00 2001 From: rockjam Date: Mon, 25 Jul 2016 19:06:49 +0300 Subject: [PATCH 193/414] feat(server:groups): group async members --- .../actor-core/src/main/protobuf/group.proto | 6 ++ .../server/group/GroupCommandHandlers.scala | 18 ++++ .../actor/server/group/GroupProcessor.scala | 16 ++-- .../im/actor/server/group/GroupState.scala | 13 ++- .../server/group/MemberCommandHandlers.scala | 92 ++++++++++++++----- .../actor/server/sequence/Optimization.scala | 4 +- 6 files changed, 106 insertions(+), 43 deletions(-) diff --git a/actor-server/actor-core/src/main/protobuf/group.proto b/actor-server/actor-core/src/main/protobuf/group.proto index 654125b819..b0fa464429 100644 --- a/actor-server/actor-core/src/main/protobuf/group.proto +++ b/actor-server/actor-core/src/main/protobuf/group.proto @@ -156,6 +156,12 @@ message GroupEvents { required int32 executor_user_id = 2; } + message MembersBecameAsync { + option (scalapb.message).extends = "im.actor.server.group.GroupEvent"; + + required int64 ts = 1 [(scalapb.field).type = "java.time.Instant"]; + } + message GroupDeleted { option (scalapb.message).extends = "im.actor.server.group.GroupEvent"; diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala index 6ccef21e6c..0b0b37ac66 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala @@ -172,4 +172,22 @@ private[group] trait GroupCommandHandlers protected def isValidTitle(title: String) = title.nonEmpty && title.length < 255 + protected def updateCanCall(currState: GroupState): Unit = { + currState.memberIds foreach { userId ⇒ + permissionsUpdates(userId, currState) foreach { update ⇒ + seqUpdExt.deliverUserUpdate(userId, update) + } + } + } + + protected def makeMembersAsync(): Unit = { + persist(MembersBecameAsync(Instant.now)) { evt ⇒ + val newState = commit(evt) + + seqUpdExt.broadcastPeopleUpdate( + userIds = newState.memberIds, + update = UpdateGroupMembersBecameAsync(groupId) + ) + } + } } diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala index 3c7488b586..4a5856c146 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala @@ -93,7 +93,8 @@ object GroupProcessor { 22019 → classOf[GroupEvents.AdminSettingsUpdated], 22020 → classOf[GroupEvents.AdminStatusChanged], 22021 → classOf[GroupEvents.HistoryBecameShared], - 22022 → classOf[GroupEvents.GroupDeleted] + 22022 → classOf[GroupEvents.GroupDeleted], + 22023 → classOf[GroupEvents.MembersBecameAsync] ) def persistenceIdFor(groupId: Int): String = s"Group-${groupId}" @@ -187,18 +188,13 @@ private[group] final class GroupProcessor override def afterCommit(e: Event) = { super.afterCommit(e) if (recoveryFinished) { + // can't make calls in group with more than 25 members if (state.membersCount > 25) { updateCanCall(state) } - // TODO: add async members - // if(state.membersCount > 50) { updateMembersAsync(state) } - } - } - - private def updateCanCall(state: GroupState): Unit = { - state.memberIds foreach { userId ⇒ - permissionsUpdates(userId, state) foreach { update ⇒ - seqUpdExt.deliverUserUpdate(userId, update) + // from 50+ members we make group with async members + if (state.membersCount >= 50) { + makeMembersAsync() } } } diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala index b600c77d69..2cd85489de 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala @@ -83,6 +83,7 @@ private[group] object GroupState { groupType = GroupType.General, isHidden = false, isHistoryShared = false, + isAsyncMembers = false, members = Map.empty, invitedUserIds = Set.empty, accessHash = 0L, @@ -112,6 +113,7 @@ private[group] final case class GroupState( groupType: GroupType, isHidden: Boolean, isHistoryShared: Boolean, + isAsyncMembers: Boolean, // members info members: Map[Int, Member], @@ -152,13 +154,6 @@ private[group] final case class GroupState( val isDeleted = deletedAt.nonEmpty - //TODO: add on commit(not during recovery!) hook to make group with async members, when more than 100 - def isAsyncMembers = - groupType match { - case General ⇒ members.size > 100 - case Channel ⇒ true - } - def getShowableOwner(clientUserId: Int): Option[Int] = groupType match { case General ⇒ Some(creatorUserId) @@ -168,6 +163,7 @@ private[group] final case class GroupState( override def updated(e: Event): GroupState = e match { case evt: Created ⇒ val typeOfGroup = evt.typ.getOrElse(GroupType.General) + val isMemberAsync = typeOfGroup.isChannel this.copy( id = evt.groupId, createdAt = Some(evt.ts), @@ -181,6 +177,7 @@ private[group] final case class GroupState( groupType = typeOfGroup, isHidden = evt.isHidden getOrElse false, isHistoryShared = evt.isHistoryShared getOrElse false, + isAsyncMembers = isMemberAsync, members = ( evt.userIds map { userId ⇒ userId → @@ -267,6 +264,8 @@ private[group] final case class GroupState( this.copy(adminSettings = AdminSettings.fromBitMask(bitMask)) case HistoryBecameShared(_, _) ⇒ this.copy(isHistoryShared = true) + case MembersBecameAsync(_) ⇒ + this.copy(isAsyncMembers = true) case GroupDeleted(ts, _) ⇒ // FIXME: don't implement snapshots, before figure out deleted groups behavior this.copy( diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/MemberCommandHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/MemberCommandHandlers.scala index 06b19c0976..37cce140ef 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/MemberCommandHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/MemberCommandHandlers.scala @@ -37,7 +37,6 @@ private[group] trait MemberCommandHandlers extends GroupsImplicits { val dateMillis = evt.ts.toEpochMilli val memberIds = newState.memberIds - val apiMembers = newState.members.values.map(_.asStruct).toVector // if user ever been in this group - we should push these updates // TODO: unify isHistoryShared usage @@ -46,11 +45,26 @@ private[group] trait MemberCommandHandlers extends GroupsImplicits { optDrop ++: refreshGroupUpdates(newState, cmd.inviteeUserId) } - val membersUpdateNew: Update = - if (newState.groupType.isChannel) // if channel, or group is big enough - UpdateGroupMembersCountChanged(groupId, newState.membersCount) - else - UpdateGroupMembersUpdated(groupId, apiMembers) + // For groups with not async members we should push Diff for members, and all Members for invitee + // For groups with async members we should push UpdateGroupMembersCountChanged for both invitee and members + val (inviteeUpdateNew, membersUpdateNew): (Update, Update) = + if (newState.isAsyncMembers) { + val u = UpdateGroupMembersCountChanged(groupId, newState.membersCount) + (u, u) + } else { + val apiMembers = newState.members.values.map(_.asStruct).toVector + val inviteeMember = apiMembers.find(_.userId == cmd.inviteeUserId) + + ( + UpdateGroupMembersUpdated(groupId, apiMembers), + UpdateGroupMemberDiff( + groupId, + addedMembers = inviteeMember.toVector, + membersCount = newState.membersCount, + removedUsers = Vector.empty + ) + ) + } val inviteeUpdateObsolete = UpdateGroupInviteObsolete( groupId, @@ -73,11 +87,11 @@ private[group] trait MemberCommandHandlers extends GroupsImplicits { def inviteGROUPUpdates: Future[SeqStateDate] = for { - // push updated members list to inviteeUserId, + // push updated members list/count to inviteeUserId, // make it `FatSeqUpdate` if this user invited to group for first time. _ ← seqUpdExt.deliverUserUpdate( userId = cmd.inviteeUserId, - membersUpdateNew, + update = inviteeUpdateNew, pushRules = seqUpdExt.pushRules(isFat = !inviteeIsExUser, Some(PushTexts.Invited)), deliveryId = s"invite_${groupId}_${cmd.randomId}" ) @@ -87,7 +101,7 @@ private[group] trait MemberCommandHandlers extends GroupsImplicits { seqUpdExt.deliverUserUpdate(userId = cmd.inviteeUserId, update) } - // push updated members list to all group members except inviteeUserId + // push updated members difference to all group members except inviteeUserId SeqState(seq, state) ← seqUpdExt.broadcastClientUpdate( userId = cmd.inviterUserId, authId = cmd.inviterAuthId, @@ -112,7 +126,7 @@ private[group] trait MemberCommandHandlers extends GroupsImplicits { // push updated members count to inviteeUserId _ ← seqUpdExt.deliverUserUpdate( userId = cmd.inviteeUserId, - membersUpdateNew, + update = inviteeUpdateNew, pushRules = seqUpdExt.pushRules(isFat = false, Some(PushTexts.Invited)), deliveryId = s"invite_${groupId}_${cmd.randomId}" ) @@ -230,11 +244,35 @@ private[group] trait MemberCommandHandlers extends GroupsImplicits { optDrop ++: (if (wasInvited) Vector.empty[Update] else refreshGroupUpdates(newState, cmd.joiningUserId)) } - val membersUpdateNew: Update = - if (newState.groupType.isChannel) // if channel, or group is big enough - UpdateGroupMembersCountChanged(groupId, newState.membersCount) - else - UpdateGroupMembersUpdated(groupId, apiMembers) // will update date when member got into group + // For groups with not async members we should push: + // • Diff for members; + // • Diff for joining user if he was previously invited; + // • Members for joining user if he wasn't previously invited. + // + // For groups with async members we should push: + // • UpdateGroupMembersCountChanged for both joining user and members + val (joiningUpdateNew, membersUpdateNew): (Update, Update) = + if (newState.isAsyncMembers) { + val u = UpdateGroupMembersCountChanged(groupId, newState.membersCount) + (u, u) + } else { + val joiningMember = apiMembers.find(_.userId == cmd.joiningUserId) + val diff = UpdateGroupMemberDiff( + groupId, + addedMembers = joiningMember.toVector, + membersCount = newState.membersCount, + removedUsers = Vector.empty + ) + + if (wasInvited) { + (diff, diff) + } else { + ( + UpdateGroupMembersUpdated(groupId, apiMembers), + diff + ) + } + } // TODO: not sure how it should be in old API val membersUpdateObsolete = UpdateGroupMembersUpdateObsolete(groupId, apiMembers) @@ -258,19 +296,19 @@ private[group] trait MemberCommandHandlers extends GroupsImplicits { seqUpdExt.deliverUserUpdate(userId = cmd.joiningUserId, update) } - // push updated members list to joining user, + // push updated members list/count/difference to joining user, // make it `FatSeqUpdate` if this user invited to group for first time. // TODO???: isFat = !wasInvited - is it correct? SeqState(seq, state) ← seqUpdExt.deliverClientUpdate( userId = cmd.joiningUserId, authId = cmd.joiningUserAuthId, - update = membersUpdateNew, + update = joiningUpdateNew, pushRules = seqUpdExt.pushRules(isFat = !wasInvited, None), //!wasInvited means that user came for first time here deliveryId = s"join_${groupId}_${randomId}" ) - // push updated members list to all group members except joiningUserId + // push updated members list/count to all group members except joiningUserId _ ← seqUpdExt.broadcastPeopleUpdate( memberIds - cmd.joiningUserId, membersUpdateNew, @@ -297,7 +335,7 @@ private[group] trait MemberCommandHandlers extends GroupsImplicits { SeqState(seq, state) ← seqUpdExt.deliverClientUpdate( userId = cmd.joiningUserId, authId = cmd.joiningUserAuthId, - update = membersUpdateNew, + update = joiningUpdateNew, deliveryId = s"join_${groupId}_${randomId}" ) @@ -358,15 +396,17 @@ private[group] trait MemberCommandHandlers extends GroupsImplicits { val updatePermissions = permissionsUpdates(cmd.userId, currState = state.updated(leftEvent)) val membersUpdateNew = - if (state.groupType.isChannel) { // if channel, or group is big enough + if (state.isAsyncMembers) { UpdateGroupMembersCountChanged( groupId, membersCount = state.membersCount - 1 ) } else { - UpdateGroupMembersUpdated( + UpdateGroupMemberDiff( groupId, - members = state.members.filterNot(_._1 == cmd.userId).values.map(_.asStruct).toVector + removedUsers = Vector(cmd.userId), + addedMembers = Vector.empty, + membersCount = state.membersCount - 1 ) } @@ -487,15 +527,17 @@ private[group] trait MemberCommandHandlers extends GroupsImplicits { val updatePermissions = permissionsUpdates(cmd.kickedUserId, newState) val membersUpdateNew: Update = - if (newState.groupType.isChannel) { // if channel, or group is big enough + if (newState.isAsyncMembers) { UpdateGroupMembersCountChanged( groupId, membersCount = newState.membersCount ) } else { - UpdateGroupMembersUpdated( + UpdateGroupMemberDiff( groupId, - members = newState.members.values.map(_.asStruct).toVector + removedUsers = Vector(cmd.kickedUserId), + addedMembers = Vector.empty, + membersCount = newState.membersCount ) } diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/sequence/Optimization.scala b/actor-server/actor-core/src/main/scala/im/actor/server/sequence/Optimization.scala index 5e6df967d1..01f6e81092 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/sequence/Optimization.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/sequence/Optimization.scala @@ -3,6 +3,7 @@ package im.actor.server.sequence import com.google.protobuf.ByteString import im.actor.api.rpc.counters.UpdateCountersChanged import im.actor.api.rpc.groups._ +import im.actor.api.rpc.messaging.UpdateChatDropCache import im.actor.api.rpc.sequence.{ ApiUpdateOptimization, UpdateEmptyUpdate } import im.actor.server.messaging.MessageParsing import im.actor.server.model.SerializedUpdate @@ -44,7 +45,8 @@ object Optimization extends MessageParsing { UpdateGroupMemberAdminChanged.header, UpdateGroupShortNameChanged.header, UpdateGroupFullPermissionsChanged.header, - UpdateGroupPermissionsChanged.header + UpdateGroupPermissionsChanged.header, + UpdateChatDropCache.header ) if (deliveryTag == GroupV2) emptyUpdate From 623750ca0d0043d504685c6dd5d8d285bca5c03c Mon Sep 17 00:00:00 2001 From: rockjam Date: Mon, 25 Jul 2016 19:21:42 +0300 Subject: [PATCH 194/414] fix(server:groups): update drop cache --- .../im/actor/server/group/MemberCommandHandlers.scala | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/MemberCommandHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/MemberCommandHandlers.scala index 37cce140ef..271d936061 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/MemberCommandHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/MemberCommandHandlers.scala @@ -38,7 +38,6 @@ private[group] trait MemberCommandHandlers extends GroupsImplicits { val dateMillis = evt.ts.toEpochMilli val memberIds = newState.memberIds - // if user ever been in this group - we should push these updates // TODO: unify isHistoryShared usage val inviteeUpdatesNew: Vector[Update] = { val optDrop = if (newState.isHistoryShared) Some(UpdateChatDropCache(apiGroupPeer)) else None @@ -240,8 +239,12 @@ private[group] trait MemberCommandHandlers extends GroupsImplicits { // cause they were pushed already on invite step // TODO: unify isHistoryShared usage val joiningUserUpdatesNew: Vector[Update] = { - val optDrop = if (newState.isHistoryShared) Some(UpdateChatDropCache(apiGroupPeer)) else None - optDrop ++: (if (wasInvited) Vector.empty[Update] else refreshGroupUpdates(newState, cmd.joiningUserId)) + if (wasInvited) { + Vector.empty[Update] + } else { + val optDrop = if (newState.isHistoryShared) Some(UpdateChatDropCache(apiGroupPeer)) else None + optDrop ++: refreshGroupUpdates(newState, cmd.joiningUserId) + } } // For groups with not async members we should push: @@ -643,7 +646,6 @@ private[group] trait MemberCommandHandlers extends GroupsImplicits { // Updates that will be sent to user, when he enters group. // Helps clients that have this group to refresh it's data. private def refreshGroupUpdates(newState: GroupState, userId: Int): Vector[Update] = Vector( - UpdateChatDropCache(apiGroupPeer), UpdateGroupMemberChanged(groupId, isMember = true), UpdateGroupAboutChanged(groupId, newState.about), UpdateGroupAvatarChanged(groupId, newState.avatar), From ee67c25ecb8de0899154b86588654fa9b14db82e Mon Sep 17 00:00:00 2001 From: rockjam Date: Mon, 25 Jul 2016 19:23:51 +0300 Subject: [PATCH 195/414] refactor(server:groups): add some logging --- .../main/scala/im/actor/server/group/GroupCommandHandlers.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala index 0b0b37ac66..0eb5380506 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala @@ -173,6 +173,7 @@ private[group] trait GroupCommandHandlers protected def isValidTitle(title: String) = title.nonEmpty && title.length < 255 protected def updateCanCall(currState: GroupState): Unit = { + log.debug(s"Group {} can call updated", groupId) currState.memberIds foreach { userId ⇒ permissionsUpdates(userId, currState) foreach { update ⇒ seqUpdExt.deliverUserUpdate(userId, update) @@ -183,6 +184,7 @@ private[group] trait GroupCommandHandlers protected def makeMembersAsync(): Unit = { persist(MembersBecameAsync(Instant.now)) { evt ⇒ val newState = commit(evt) + log.debug(s"Group {} became async members", groupId) seqUpdExt.broadcastPeopleUpdate( userIds = newState.memberIds, From 735146abdf81cc2c827d04c8096fa06ded8a93f5 Mon Sep 17 00:00:00 2001 From: rockjam Date: Mon, 25 Jul 2016 20:08:51 +0300 Subject: [PATCH 196/414] chore(server): update actor.json --- .../actor-core/src/main/actor-api/actor.json | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/actor-server/actor-core/src/main/actor-api/actor.json b/actor-server/actor-core/src/main/actor-api/actor.json index 5e9fdbab71..544ad17482 100644 --- a/actor-server/actor-core/src/main/actor-api/actor.json +++ b/actor-server/actor-core/src/main/actor-api/actor.json @@ -6637,7 +6637,7 @@ { "type": "reference", "argument": "dialogs", - "category": "full", + "category": "compact", "description": " New dialgos list" } ], @@ -10415,6 +10415,12 @@ "argument": "canAdminsEditGroupInfo", "category": "full", "description": " Can admins edit group info" + }, + { + "type": "reference", + "argument": "showJoinLeaveMessages", + "category": "full", + "description": " Should join and leave messages be visible to members" } ], "expandable": "true", @@ -10438,6 +10444,11 @@ "type": "bool", "id": 4, "name": "canAdminsEditGroupInfo" + }, + { + "type": "bool", + "id": 5, + "name": "showJoinLeaveMessages" } ] } From 00e578fb0b3ab83e127a60ba1d4d98c2b89584b0 Mon Sep 17 00:00:00 2001 From: rockjam Date: Mon, 25 Jul 2016 20:24:16 +0300 Subject: [PATCH 197/414] feat(server:groups): admin setting to show join/leave messages --- .../server/group/GroupQueryHandlers.scala | 3 +- .../im/actor/server/group/GroupState.scala | 27 ++++++------ .../server/group/MemberCommandHandlers.scala | 41 ++++++++++++------- 3 files changed, 43 insertions(+), 28 deletions(-) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala index 71fad35c88..65bf91f2b4 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala @@ -167,7 +167,8 @@ trait GroupQueryHandlers { showAdminsToMembers = state.adminSettings.showAdminsToMembers, canMembersInvite = state.adminSettings.canMembersInvite, canMembersEditGroupInfo = state.adminSettings.canMembersEditGroupInfo, - canAdminsEditGroupInfo = state.adminSettings.canAdminsEditGroupInfo + canAdminsEditGroupInfo = state.adminSettings.canAdminsEditGroupInfo, + showJoinLeaveMessages = state.adminSettings.showJoinLeaveMessages ) ) } diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala index 2cd85489de..b5799b9625 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala @@ -27,26 +27,27 @@ object AdminSettings { showAdminsToMembers = true, canMembersInvite = true, canMembersEditGroupInfo = true, - canAdminsEditGroupInfo = true + canAdminsEditGroupInfo = true, + showJoinLeaveMessages = true ) val ChannelsDefault = AdminSettings( showAdminsToMembers = false, canMembersInvite = false, canMembersEditGroupInfo = false, - canAdminsEditGroupInfo = true + canAdminsEditGroupInfo = true, + showJoinLeaveMessages = false // TODO: figure it out. We don't use it by default ) // format: OFF def apiToBitMask(settings: ApiAdminSettings): Int = { - def toInt(b: Boolean) = if(b) 1 else 0 - - List( - toInt(settings.showAdminsToMembers) << 0, - toInt(settings.canMembersInvite) << 1, - toInt(settings.canMembersEditGroupInfo) << 2, - toInt(settings.canAdminsEditGroupInfo) << 3 - ).sum + def toInt(b: Boolean) = if (b) 1 else 0 + + (toInt(settings.showAdminsToMembers) << 0) + + (toInt(settings.canMembersInvite) << 1) + + (toInt(settings.canMembersEditGroupInfo) << 2) + + (toInt(settings.canAdminsEditGroupInfo) << 3) + + (toInt(settings.showJoinLeaveMessages) << 4) } def fromBitMask(mask: Int): AdminSettings = { @@ -54,7 +55,8 @@ object AdminSettings { showAdminsToMembers = (mask & (1 << 0)) != 0, canMembersInvite = (mask & (1 << 1)) != 0, canMembersEditGroupInfo = (mask & (1 << 2)) != 0, - canAdminsEditGroupInfo = (mask & (1 << 3)) != 0 + canAdminsEditGroupInfo = (mask & (1 << 3)) != 0, + showJoinLeaveMessages = (mask & (1 << 4)) != 0 ) } // format: ON @@ -64,7 +66,8 @@ private[group] final case class AdminSettings( showAdminsToMembers: Boolean, // 1 canMembersInvite: Boolean, // 2 canMembersEditGroupInfo: Boolean, // 4 - canAdminsEditGroupInfo: Boolean // 8 + canAdminsEditGroupInfo: Boolean, // 8 + showJoinLeaveMessages: Boolean // 16 ) private[group] object GroupState { diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/MemberCommandHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/MemberCommandHandlers.scala index 271d936061..fa4cf0a250 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/MemberCommandHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/MemberCommandHandlers.scala @@ -3,6 +3,7 @@ package im.actor.server.group import java.time.{ Instant, LocalDateTime, ZoneOffset } import akka.actor.Status +import akka.http.scaladsl.util.FastFuture import akka.pattern.pipe import im.actor.api.rpc.Update import im.actor.api.rpc.groups._ @@ -228,6 +229,7 @@ private[group] trait MemberCommandHandlers extends GroupsImplicits { val date = evt.ts val dateMillis = date.toEpochMilli + val showJoinMessage = newState.adminSettings.showJoinLeaveMessages val memberIds = newState.memberIds val apiMembers = newState.members.values.map(_.asStruct).toVector val randomId = ACLUtils.randomLong() @@ -318,13 +320,17 @@ private[group] trait MemberCommandHandlers extends GroupsImplicits { deliveryId = s"userjoined_${groupId}_${randomId}" ) - SeqStateDate(_, _, date) ← dialogExt.sendServerMessage( - apiGroupPeer, - senderUserId = cmd.joiningUserId, - senderAuthId = cmd.joiningUserAuthId, - randomId = randomId, - serviceMessage // no delivery tag. This updated handled this way in Groups V1 - ) + date ← if (showJoinMessage) { + dialogExt.sendServerMessage( + apiGroupPeer, + senderUserId = cmd.joiningUserId, + senderAuthId = cmd.joiningUserAuthId, + randomId = randomId, + serviceMessage // no delivery tag. This updated handled this way in Groups V1 + ) map (_.date) + } else { + FastFuture.successful(dateMillis) + } } yield SeqStateDate(seq, state, date) def joinCHANNELUpdates: Future[SeqStateDate] = @@ -393,6 +399,7 @@ private[group] trait MemberCommandHandlers extends GroupsImplicits { // no commit here. it will be after service message sent val dateMillis = evt.ts.toEpochMilli + val showLeaveMessage = state.adminSettings.showJoinLeaveMessages val updateObsolete = UpdateGroupUserLeaveObsolete(groupId, cmd.userId, dateMillis, cmd.randomId) @@ -432,14 +439,18 @@ private[group] trait MemberCommandHandlers extends GroupsImplicits { ) // send service message - SeqStateDate(_, _, date) ← dialogExt.sendServerMessage( - apiGroupPeer, - senderUserId = cmd.userId, - senderAuthId = cmd.authId, - randomId = cmd.randomId, - message = serviceMessage, - deliveryTag = Some(Optimization.GroupV2) - ) + date ← if (showLeaveMessage) { + dialogExt.sendServerMessage( + apiGroupPeer, + senderUserId = cmd.userId, + senderAuthId = cmd.authId, + randomId = cmd.randomId, + message = serviceMessage, + deliveryTag = Some(Optimization.GroupV2) + ) map (_.date) + } else { + FastFuture.successful(dateMillis) + } // push left user that he is no longer a member SeqState(seq, state) ← seqUpdExt.deliverClientUpdate( From 1ed27d5c44fdde5361b18a115371154977093488 Mon Sep 17 00:00:00 2001 From: rockjam Date: Mon, 25 Jul 2016 20:55:22 +0300 Subject: [PATCH 198/414] fix(server:groups): push join message to joining user --- .../server/group/MemberCommandHandlers.scala | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/MemberCommandHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/MemberCommandHandlers.scala index fa4cf0a250..bc7d52be6b 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/MemberCommandHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/MemberCommandHandlers.scala @@ -329,7 +329,17 @@ private[group] trait MemberCommandHandlers extends GroupsImplicits { serviceMessage // no delivery tag. This updated handled this way in Groups V1 ) map (_.date) } else { - FastFuture.successful(dateMillis) + // push join message only to joining user + seqUpdExt.deliverUserUpdate( + userId = cmd.joiningUserId, + update = serviceMessageUpdate( + cmd.joiningUserId, + dateMillis, + randomId, + serviceMessage + ), + deliveryTag = Some(Optimization.GroupV2) + ) map (_ ⇒ dateMillis) } } yield SeqStateDate(seq, state, date) @@ -354,6 +364,18 @@ private[group] trait MemberCommandHandlers extends GroupsImplicits { membersUpdateNew, deliveryId = s"userjoined_${groupId}_${randomId}" ) + + // push join message only to joining user + _ <- seqUpdExt.deliverUserUpdate( + userId = cmd.joiningUserId, + update = serviceMessageUpdate( + cmd.joiningUserId, + dateMillis, + randomId, + serviceMessage + ), + deliveryTag = Some(Optimization.GroupV2) + ) } yield SeqStateDate(seq, state, dateMillis) val result: Future[(SeqStateDate, Vector[Int], Long)] = From a03e629ee125bcb4b90798177af947796baab4a6 Mon Sep 17 00:00:00 2001 From: rockjam Date: Mon, 25 Jul 2016 21:02:50 +0300 Subject: [PATCH 199/414] fix(server:groups): push texts --- .../server/group/InfoCommandHandlers.scala | 6 ++--- .../server/group/MemberCommandHandlers.scala | 8 +++--- .../im/actor/server/group/PushTexts.scala | 25 ++++++++++++++++--- 3 files changed, 28 insertions(+), 11 deletions(-) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/InfoCommandHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/InfoCommandHandlers.scala index 7072283b75..389cfc7f0a 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/InfoCommandHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/InfoCommandHandlers.scala @@ -94,7 +94,7 @@ private[group] trait InfoCommandHandlers { randomId = cmd.randomId ) val serviceMessage = GroupServiceMessages.changedTitle(title) - val pushRules = seqUpdExt.pushRules(isFat = false, Some(PushTexts.TitleChanged)) + val pushRules = seqUpdExt.pushRules(isFat = false, Some(PushTexts.titleChanged(newState.groupType))) //TODO: remove deprecated db.run(GroupRepo.updateTitle(groupId, title, cmd.clientUserId, cmd.randomId, date = evt.ts)) @@ -168,7 +168,7 @@ private[group] trait InfoCommandHandlers { date = dateMillis ) val serviceMessage = GroupServiceMessages.changedTopic(topic) - val pushRules = seqUpdExt.pushRules(isFat = false, Some(PushTexts.TopicChanged)) + val pushRules = seqUpdExt.pushRules(isFat = false, Some(PushTexts.topicChanged(newState.groupType))) //TODO: remove deprecated db.run(GroupRepo.updateTopic(groupId, topic)) @@ -232,7 +232,7 @@ private[group] trait InfoCommandHandlers { val updateNew = UpdateGroupAboutChanged(groupId, about) val updateObsolete = UpdateGroupAboutChangedObsolete(groupId, about) val serviceMessage = GroupServiceMessages.changedAbout(about) - val pushRules = seqUpdExt.pushRules(isFat = false, Some(PushTexts.TopicChanged)) + val pushRules = seqUpdExt.pushRules(isFat = false, Some(PushTexts.topicChanged(newState.groupType))) //TODO: remove deprecated db.run(GroupRepo.updateAbout(groupId, about)) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/MemberCommandHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/MemberCommandHandlers.scala index bc7d52be6b..16a4b64d61 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/MemberCommandHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/MemberCommandHandlers.scala @@ -92,7 +92,7 @@ private[group] trait MemberCommandHandlers extends GroupsImplicits { _ ← seqUpdExt.deliverUserUpdate( userId = cmd.inviteeUserId, update = inviteeUpdateNew, - pushRules = seqUpdExt.pushRules(isFat = !inviteeIsExUser, Some(PushTexts.Invited)), + pushRules = seqUpdExt.pushRules(isFat = !inviteeIsExUser, Some(PushTexts.invited(newState.groupType))), deliveryId = s"invite_${groupId}_${cmd.randomId}" ) @@ -127,7 +127,7 @@ private[group] trait MemberCommandHandlers extends GroupsImplicits { _ ← seqUpdExt.deliverUserUpdate( userId = cmd.inviteeUserId, update = inviteeUpdateNew, - pushRules = seqUpdExt.pushRules(isFat = false, Some(PushTexts.Invited)), + pushRules = seqUpdExt.pushRules(isFat = false, Some(PushTexts.invited(newState.groupType))), deliveryId = s"invite_${groupId}_${cmd.randomId}" ) @@ -167,7 +167,7 @@ private[group] trait MemberCommandHandlers extends GroupsImplicits { _ ← seqUpdExt.deliverUserUpdate( userId = cmd.inviteeUserId, inviteeUpdateObsolete, - pushRules = seqUpdExt.pushRules(isFat = true, Some(PushTexts.Invited)), + pushRules = seqUpdExt.pushRules(isFat = true, Some(PushTexts.invited(newState.groupType))), deliveryId = s"invite_obsolete_${groupId}_${cmd.randomId}" ) @@ -366,7 +366,7 @@ private[group] trait MemberCommandHandlers extends GroupsImplicits { ) // push join message only to joining user - _ <- seqUpdExt.deliverUserUpdate( + _ ← seqUpdExt.deliverUserUpdate( userId = cmd.joiningUserId, update = serviceMessageUpdate( cmd.joiningUserId, diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/PushTexts.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/PushTexts.scala index 674d313ea7..bdb45dc401 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/PushTexts.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/PushTexts.scala @@ -3,10 +3,27 @@ package im.actor.server.group //TODO: make up to date with channels object PushTexts { val Added = "User added" - val Invited = "You are invited to a group" val Kicked = "User kicked" val Left = "User left" - val TitleChanged = "Group title changed" - val TopicChanged = "Group topic changed" - val AboutChanged = "Group about changed" + + def invited(gt: GroupType) = + if (gt.isChannel) { + "You are invited to a channel" + } else { + "You are invited to a group" + } + + def titleChanged(gt: GroupType) = + if (gt.isChannel) { + "Channel title changed" + } else { + "Group title changed" + } + + def topicChanged(gt: GroupType) = + if (gt.isChannel) { + "Channel topic changed" + } else { + "Group topic changed" + } } From 2bba8a0005308c08bec402a8ed2cfc3e7a1a67cf Mon Sep 17 00:00:00 2001 From: rockjam Date: Mon, 25 Jul 2016 21:28:10 +0300 Subject: [PATCH 200/414] fix(server): fix --- .../src/main/scala/im/actor/server/group/GroupProcessor.scala | 4 ++-- .../src/main/scala/im/actor/server/group/GroupState.scala | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala index 4a5856c146..009961ccfa 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala @@ -189,11 +189,11 @@ private[group] final class GroupProcessor super.afterCommit(e) if (recoveryFinished) { // can't make calls in group with more than 25 members - if (state.membersCount > 25) { + if (state.membersCount == 26) { updateCanCall(state) } // from 50+ members we make group with async members - if (state.membersCount >= 50) { + if (state.membersCount >= 50 && !state.isAsyncMembers) { makeMembersAsync() } } diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala index b5799b9625..01bc29da71 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala @@ -385,7 +385,7 @@ private[group] final case class GroupState( (toInt(canViewMembers(userId)) << 1) + (toInt(canInviteMembers(userId)) << 2) + (toInt(canInviteViaLink(userId)) << 3) + - (toInt(canCall(userId)) << 4) + + (toInt(canCall) << 4) + (toInt(canEditAdminSettings(userId)) << 5) + (toInt(canViewAdmins(userId)) << 6) + (toInt(canEditAdmins(userId)) << 7) + @@ -433,7 +433,7 @@ private[group] final case class GroupState( /** * All members can call, if group has less than 25 members, and is not a channel */ - private def canCall(clientUserId: Int) = !groupType.isChannel && membersCount <= 25 + def canCall = !groupType.isChannel && membersCount <= 25 // only owner can change admin settings def canEditAdminSettings(clientUserId: Int): Boolean = isOwner(clientUserId) From 0d6b9af16beb6648c1a3441bfb4b66de0b1aaad3 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Mon, 25 Jul 2016 22:02:59 +0300 Subject: [PATCH 201/414] fix(android): join group from search --- .../search/GlobalSearchBaseFragment.java | 26 ++++++++----------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/search/GlobalSearchBaseFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/search/GlobalSearchBaseFragment.java index 6df76f80e9..bb58b99b2a 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/search/GlobalSearchBaseFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/search/GlobalSearchBaseFragment.java @@ -275,21 +275,17 @@ public boolean onLongClicked(SearchEntity item) { @Override public void onClicked(SearchEntity item) { int peerId = item.getPeer().getPeerId(); - execute(messenger().addContact(peerId), R.string.progress_common, new CommandCallback() { - @Override - public void onResult(Boolean res2) { - startActivity(Intents.openPrivateDialog(peerId, - true, - getActivity())); - } - - @Override - public void onError(Exception e) { - startActivity(Intents.openPrivateDialog(peerId, - true, - getActivity())); - } - }); + switch (item.getPeer().getPeerType()) { + case PRIVATE: + messenger().addContact(peerId); + startActivity(Intents.openPrivateDialog(peerId, true, getActivity())); + break; + + case GROUP: + messenger().joinGroup(peerId); + startActivity(Intents.openGroupDialog(peerId, false, getActivity())); + break; + } } @Override From d2779cf7bb21e12320430298255df5e0faa46692 Mon Sep 17 00:00:00 2001 From: rockjam Date: Thu, 28 Jul 2016 20:00:40 +0300 Subject: [PATCH 202/414] test(server): UpdateChatGroupsChanged hack --- .../api/rpc/service/SequenceServiceSpec.scala | 85 ++++++++++++++++++- 1 file changed, 81 insertions(+), 4 deletions(-) diff --git a/actor-server/actor-tests/src/test/scala/im/actor/server/api/rpc/service/SequenceServiceSpec.scala b/actor-server/actor-tests/src/test/scala/im/actor/server/api/rpc/service/SequenceServiceSpec.scala index 70166d15e9..6f0e15d1dd 100644 --- a/actor-server/actor-tests/src/test/scala/im/actor/server/api/rpc/service/SequenceServiceSpec.scala +++ b/actor-server/actor-tests/src/test/scala/im/actor/server/api/rpc/service/SequenceServiceSpec.scala @@ -4,12 +4,15 @@ import com.typesafe.config.ConfigFactory import im.actor.api.rpc._ import im.actor.api.rpc.contacts.UpdateContactsAdded import im.actor.api.rpc.counters.{ ApiAppCounters, UpdateCountersChanged } -import im.actor.api.rpc.messaging.{ ApiTextMessage, UpdateMessageContentChanged } +import im.actor.api.rpc.messaging.{ ApiTextMessage, UpdateChatGroupsChanged, UpdateMessageContentChanged } import im.actor.api.rpc.misc.ResponseSeq import im.actor.api.rpc.peers.{ ApiPeer, ApiPeerType } import im.actor.api.rpc.sequence.{ ApiUpdateContainer, ApiUpdateOptimization, ResponseGetDifference, UpdateEmptyUpdate } import im.actor.server._ -import im.actor.server.api.rpc.service.sequence.SequenceServiceConfig +import im.actor.server.api.rpc.service.groups.{ GroupInviteConfig, GroupsServiceImpl } +import im.actor.server.api.rpc.service.messaging.MessagingServiceImpl +import im.actor.server.api.rpc.service.sequence.{ SequenceServiceConfig, SequenceServiceImpl } +import im.actor.server.dialog.{ DialogExtension, DialogGroupKeys } import im.actor.server.sequence.{ CommonState, CommonStateVersion, UserSequence } import scala.concurrent.Future @@ -25,6 +28,7 @@ final class SequenceServiceSpec extends BaseAppSuite({ }) with ImplicitSessionRegion with ImplicitAuthService with SeqUpdateMatchers + with GroupsServiceHelpers with ImplicitSequenceService { behavior of "Sequence service" @@ -34,11 +38,13 @@ final class SequenceServiceSpec extends BaseAppSuite({ it should "not produce empty difference if there is one update bigger than difference size limit" in bigUpdate it should "map updates correctly" in mapUpdates it should "exclude optimized updates from sequence" in optimizedUpdates + it should "return single UpdateChatGroupsChanged in difference as if it was applied after all message reads" in chatGroupChanged private val config = SequenceServiceConfig.load().get - implicit lazy val service = new sequence.SequenceServiceImpl(config) - implicit lazy val msgService = messaging.MessagingServiceImpl() + implicit lazy val service = new SequenceServiceImpl(config) + implicit lazy val msgService = MessagingServiceImpl() + implicit lazy val groupService = new GroupsServiceImpl(GroupInviteConfig("")) def getState() = { val (user, authId, authSid, _) = createUser() @@ -271,4 +277,75 @@ final class SequenceServiceSpec extends BaseAppSuite({ } } } + + def chatGroupChanged(): Unit = { + val (alice, aliceAuthId, aliceAuthSid, _) = createUser() + + val sessionId = createSessionId() + val aliceClientData = ClientData(aliceAuthId, sessionId, Some(AuthData(alice.id, aliceAuthSid, 42))) + + val (bob, bobAuthId, bobAuthSid, _) = createUser() + + val group = { + implicit val cd = aliceClientData + createGroup("Some group", Set(bob.id)).groupPeer + } + + val groupReadDates = { + implicit val cd = ClientData(bobAuthId, sessionId, Some(AuthData(bob.id, bobAuthSid, 42))) + 1 to 20 map { i ⇒ + sendMessageToGroup(group.groupId, textMessage(s"Hello in group $i"))._2.date + } + } + + val bobReadDates = { + implicit val cd = ClientData(bobAuthId, sessionId, Some(AuthData(bob.id, bobAuthSid, 42))) + 1 to 20 map { i ⇒ + sendMessageToUser(alice.id, s"Hello $i")._2.date + } + } + + { + implicit val cd = aliceClientData + whenReady(msgService.handleFavouriteDialog(getOutPeer(bob.id, aliceAuthId)))(identity) + + // read 5 messages, 15 left + whenReady(msgService.handleMessageRead(getOutPeer(bob.id, aliceAuthId), bobReadDates(4)))(identity) + + whenReady(msgService.handleUnfavouriteDialog(getOutPeer(bob.id, aliceAuthId)))(identity) + // read 10 messages, 10 left + whenReady(msgService.handleMessageRead(getOutPeer(bob.id, aliceAuthId), bobReadDates(9)))(identity) + + // read all messages in group + whenReady(msgService.handleMessageRead(group.asOutPeer, groupReadDates.last))(identity) + } + + { + implicit val cd = aliceClientData + + whenReady(service.handleGetDifference(0, Array.empty, Vector.empty)) { res ⇒ + inside(res) { + case Ok(rsp: ResponseGetDifference) ⇒ + val groupedChatsUpdates = rsp.updates.filter(_.updateHeader == UpdateChatGroupsChanged.header) + groupedChatsUpdates should have length 1 + } + } + + expectUpdate(classOf[UpdateChatGroupsChanged]) { upd ⇒ + val optDirect = upd.dialogs.find(_.key == DialogGroupKeys.Direct) + optDirect shouldBe defined + + val bobsDialog = optDirect.get.dialogs.find(_.peer.id == bob.id) + bobsDialog.get.counter shouldEqual 10 + + val optGroups = upd.dialogs.find(_.key == DialogGroupKeys.Groups) + optGroups shouldBe defined + + val groupDialog = optGroups.get.dialogs.find(_.peer.id == group.groupId) + groupDialog.get.counter shouldEqual 0 + } + } + + } + } From be5876313e8b0c30abd9e019ad2b029a41d70978 Mon Sep 17 00:00:00 2001 From: rockjam Date: Thu, 28 Jul 2016 20:19:37 +0300 Subject: [PATCH 203/414] test(server): move groups spec to v1 --- .../api/rpc/service/{ => groups/v1}/GroupsServiceSpec.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename actor-server/actor-tests/src/test/scala/im/actor/server/api/rpc/service/{ => groups/v1}/GroupsServiceSpec.scala (99%) diff --git a/actor-server/actor-tests/src/test/scala/im/actor/server/api/rpc/service/GroupsServiceSpec.scala b/actor-server/actor-tests/src/test/scala/im/actor/server/api/rpc/service/groups/v1/GroupsServiceSpec.scala similarity index 99% rename from actor-server/actor-tests/src/test/scala/im/actor/server/api/rpc/service/GroupsServiceSpec.scala rename to actor-server/actor-tests/src/test/scala/im/actor/server/api/rpc/service/groups/v1/GroupsServiceSpec.scala index f9548596d9..f8ae7eeea3 100644 --- a/actor-server/actor-tests/src/test/scala/im/actor/server/api/rpc/service/GroupsServiceSpec.scala +++ b/actor-server/actor-tests/src/test/scala/im/actor/server/api/rpc/service/groups/v1/GroupsServiceSpec.scala @@ -1,4 +1,4 @@ -package im.actor.server.api.rpc.service +package im.actor.server.api.rpc.service.groups.v1 import im.actor.api.rpc._ import im.actor.api.rpc.counters.UpdateCountersChanged From a5f54ea3b68ec0785c25ae73b50643c2102277bf Mon Sep 17 00:00:00 2001 From: rockjam Date: Thu, 28 Jul 2016 20:59:47 +0300 Subject: [PATCH 204/414] test(server): start writing Group V2 tests --- .../service/groups/v2/GroupV2GroupSpec.scala | 170 ++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 actor-server/actor-tests/src/test/scala/im/actor/server/api/rpc/service/groups/v2/GroupV2GroupSpec.scala diff --git a/actor-server/actor-tests/src/test/scala/im/actor/server/api/rpc/service/groups/v2/GroupV2GroupSpec.scala b/actor-server/actor-tests/src/test/scala/im/actor/server/api/rpc/service/groups/v2/GroupV2GroupSpec.scala new file mode 100644 index 0000000000..752ed6f3b9 --- /dev/null +++ b/actor-server/actor-tests/src/test/scala/im/actor/server/api/rpc/service/groups/v2/GroupV2GroupSpec.scala @@ -0,0 +1,170 @@ +package im.actor.server.api.rpc.service.groups.v2 + +import cats.data.Xor +import im.actor.api.rpc.groups.{ ApiGroupType, UpdateGroupInviteObsolete } +import im.actor.api.rpc.messaging.UpdateMessage +import im.actor.api.rpc.peers.{ ApiGroupOutPeer, ApiUserOutPeer } +import im.actor.api.rpc.{ AuthData, ClientData, Ok, PeersImplicits, RpcError, RpcResponse } +import im.actor.api.rpc.sequence.ApiUpdateOptimization +import im.actor.server._ +import im.actor.server.api.rpc.service.groups.{ GroupInviteConfig, GroupsServiceImpl } +import im.actor.server.group.{ GroupExtension, GroupServiceMessages, GroupType } +import im.actor.server.sequence.SeqState +import im.actor.server.user.UserExtension +import im.actor.util.ThreadLocalSecureRandom + +import scala.concurrent.Future + +class GroupV2GroupSpec extends BaseAppSuite + // with GroupsServiceHelpers + with MessageParsing + // with MessagingSpecHelpers + with ImplicitSequenceService + with ImplicitAuthService + with ImplicitMessagingService + with ImplicitSessionRegion + with SeqUpdateMatchers + with PeersImplicits { + + behavior of "Groups V2 API" + + it should "create group and return correct members list" in create + + it should "allow ONLY group members to invite other users" in invite + + // it should "allow only group admin to change about" in changeAbout + + // it should "allow only group admin to revoke integration token" in revokeToken + + // join + // invite + // kick + // leave + // change actions by admin + // change actions by non-admin + + val optimizations = Vector(ApiUpdateOptimization.GROUPS_V2, ApiUpdateOptimization.STRIP_ENTITIES) + + val groupInviteConfig = GroupInviteConfig("http://actor.im") + val groupExt = GroupExtension(system) + + val groupService = new GroupsServiceImpl(groupInviteConfig) + + val userExt = UserExtension(system) + + def create(): Unit = { + val (alice, aliceAuthId, aliceAuthSid, _) = createUser() + val sessionId = createSessionId() + + implicit val clientData = ClientData(aliceAuthId, sessionId, Some(AuthData(alice.id, aliceAuthSid, 42))) + val aliceOutPeer = getUserOutPeer(alice.id, aliceAuthId) + + val members = 1 to 20 map { i ⇒ + val (user, authId, authSid, _) = createUser() + getUserOutPeer(user.id, aliceAuthId) + } + + val title = "V2 first group" + val randomId = nextRandomId() + val createResp = response(groupService.handleCreateGroup( + randomId, + title = title, + users = members, + groupType = None, + optimizations = optimizations + )) + + // members checks + createResp.users shouldBe empty // strip entities optimization + createResp.userPeers should not be empty + createResp.userPeers should have length 21 // 20 invited users + 1 creator + createResp.group.membersCount shouldEqual Some(21) + createResp.userPeers should contain theSameElementsAs (members :+ aliceOutPeer) + + // group properties checks + createResp.group.groupType shouldEqual Some(ApiGroupType.GROUP) + createResp.group.title shouldEqual title + createResp.group.isHidden shouldEqual Some(false) + + // membership checks + createResp.group.isMember shouldEqual Some(true) + createResp.group.isAdmin shouldEqual Some(true) + createResp.group.creatorUserId shouldEqual alice.id + + // Group V2 updates + expectUpdate(classOf[UpdateMessage]) { upd ⇒ + upd.randomId shouldEqual randomId + upd.message shouldEqual GroupServiceMessages.groupCreated + } + + // There should be no Group V1 updates + expectNoUpdate(emptyState, classOf[UpdateGroupInviteObsolete]) + } + + def invite() = { + val (alice, aliceAuthId, aliceAuthSid, _) = createUser() + val (bob, bobAuthId, bobAuthSid, _) = createUser() + val (carol, _, _, _) = createUser() + + val sessionId = createSessionId() + + val aliceClientData = ClientData(aliceAuthId, sessionId, Some(AuthData(alice.id, aliceAuthSid, 42))) + val bobClientData = ClientData(bobAuthId, sessionId, Some(AuthData(bob.id, bobAuthSid, 42))) + + val randomId = nextRandomId() + val (seqState, groupOutPeer) = { + implicit val cd = aliceClientData + val createResp = response(groupService.handleCreateGroup( + randomId, + title = "Invite check group", + users = Vector.empty, + groupType = None, + optimizations = optimizations + )) + val group = createResp.group + group.membersCount shouldEqual Some(1) + group.members should have length 1 + group.members map (_.userId) shouldEqual Vector(alice.id) + + mkSeqState(createResp.seq, createResp.state) → ApiGroupOutPeer(group.id, group.accessHash) + } + + // Don't allow non-member to invite other users + { + implicit val cd = bobClientData + whenReady(groupService.handleInviteUser( + groupOutPeer, + nextRandomId(), + getUserOutPeer(carol.id, bobAuthId), + optimizations + )) { _ should matchForbidden } + } + + // Allow group member to invite other users + { + implicit val cd = aliceClientData + val randomId = nextRandomId() + whenReady(groupService.handleInviteUser( + groupOutPeer, + randomId, + getUserOutPeer(bob.id, aliceAuthId), + optimizations + )) { resp ⇒ + + } + + } + + } + + private def response[A <: RpcResponse](result: Future[RpcError Xor A]): A = { + val r = result.futureValue + r should matchPattern { + case Ok(_) ⇒ + } + r.getOrElse(fail("Rpc response was not OK")) + } + + private def nextRandomId() = ThreadLocalSecureRandom.current().nextLong() + +} From 837274a3c723a1e96de836a07bd91de1a9bbf407 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Fri, 29 Jul 2016 15:22:40 +0300 Subject: [PATCH 205/414] feat(iOS): Spanish localization of channels --- .../Resources/es.lproj/Localizable.strings | 106 +++++++++--------- 1 file changed, 53 insertions(+), 53 deletions(-) diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/es.lproj/Localizable.strings b/actor-sdk/sdk-core-ios/ActorSDK/Resources/es.lproj/Localizable.strings index 4f845ba756..607ff42e44 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Resources/es.lproj/Localizable.strings +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/es.lproj/Localizable.strings @@ -287,74 +287,74 @@ "GroupViewMembers" = "Miembros"; -"GroupDescription" = "Description"; +"GroupDescription" = "Descripción”; -"GroupAdministration" = "Administration"; +"GroupAdministration" = "Administrador”; -"GroupTypeTitle" = "Group Type"; +"GroupTypeTitle" = "Tipo de Grupo"; -"GroupTypeTitleChannel" = "Channel Type"; +"GroupTypeTitleChannel" = "Tipo de Canal"; -"GroupTypePublic" = "Public"; +"GroupTypePublic" = "Público”; -"GroupTypePrivate" = "Private"; +"GroupTypePrivate" = "Privado”; -"ChannelTypePublic" = "Public"; +"ChannelTypePublic" = "Público”; -"ChannelTypePrivate" = "Private"; +"ChannelTypePrivate" = "Privado”; -"GroupTypePublicFull" = "Public Group"; +"GroupTypePublicFull" = "Grupo público“; -"GroupTypePrivateFull" = "Private Group"; +"GroupTypePrivateFull" = "Grupo privado"; -"ChannelTypePublicFull" = "Public Channel"; +"ChannelTypePublicFull" = "Canal público"; -"ChannelTypePrivateFull" = "Private Channel"; +"ChannelTypePrivateFull" = "Canal privado"; -"GroupPermissionsHint" = "Control what is possible in this group"; +"GroupPermissionsHint" = "Controlar lo que es posible en este grupo"; -"GroupPermissionsHintChannel" = "Control what is possible in this channel"; +"GroupPermissionsHintChannel" = "Controlar lo que es posible en este canal"; -"GroupTypeHintPublic" = "Public groups can be found in search and anyone can join"; +"GroupTypeHintPublic" = "Los grupos públicos se pueden encontrar en la búsqueda y cualquiera puede unirse"; -"GroupTypeHintPrivate" = "Private groups can be joined only via personal invitation"; +"GroupTypeHintPrivate" = "Los grupos privados pueden unirse sólo a través de invitación personal"; -"GroupTypeHintPublicChannel" = "Public channels can be found in search and anyone can join"; +"GroupTypeHintPublicChannel" = "Los canales públicos se pueden encontrar en la búsqueda y cualquiera puede unirse"; -"GroupTypeHintPrivateChannel" = "Private channels can be joined only via personal invitation"; +"GroupTypeHintPrivateChannel" = "Los canales privados se pueden unir únicamente a través de invitación personal"; "GroupTypeLinkHint" = "People can share this link with others and find your group using search"; -"GroupTypeLinkHintChannel" = "People can share this link with others and find your channel using search"; +"GroupTypeLinkHintChannel" = "Las personas pueden compartir este enlace con los demás y encontrar su grupo mediante la búsqueda"; -"GroupDeleteHint" = "You will lose all messages in this group"; +"GroupDeleteHint" = "Perderá todos los mensajes de este grupo"; -"GroupDeleteHintChannel" = "You will lose all messages in this channel"; +"GroupDeleteHintChannel" = "Usted perderá todos los mensajes de este canal"; -"GroupShareTitle" = "Shared History"; +"GroupShareTitle" = "Historial compartido”; -"GroupShareEnabled" = "Shared"; +"GroupShareEnabled" = "Compartir"; -"GroupShareHint" = "All members will see all messages"; +"GroupShareHint" = "Todos los miembros podrán ver todos los mensajes"; -"GroupShareMessage" = "Are you sure want to share all messages to all members? This action is irreversible."; +"GroupShareMessage" = "¿Seguro desea compartir todos los mensajes a todos los miembros? Esta acción es irreversible."; -"GroupShareAction" = "Share"; +"GroupShareAction" = "Compartir"; -"GroupEditTitle" = "Edit Group"; +"GroupEditTitle" = "Editar Grupo“; -"GroupEditTitleChannel" = "Edit Channel"; +"GroupEditTitleChannel" = "Editar Canal”; -"GroupEditName" = "Group Name"; +"GroupEditName" = "Nombre de Grupo"; -"GroupEditNameChannel" = "Channel Name"; +"GroupEditNameChannel" = "Nombre del Canal"; -"GroupEditDescription" = "Description"; +"GroupEditDescription" = "Descripción”; @@ -413,20 +413,20 @@ "CreateGroup" = "Crear grupo"; -"CreateChannel" = "Create channel"; +"CreateChannel" = "Crear canal”; "CreateGroupTitle" = "Crear grupo"; "CreateGroupHint" = "Por favor, introduce un nombre de grupo e inserta una imagen."; -"CreateGroupNamePlaceholder" = "Grupo Asunto"; +"CreateGroupNamePlaceholder" = "Asunto del Grupo”; -"CreateChannelTitle" = "Create Channel"; +"CreateChannelTitle" = "Crear Canal“; -"CreateChannelHint" = "Please provide the channel name and an optional channel icon"; +"CreateChannelHint" = "Por favor, proporcione el nombre del canal y un icono de canal opcional"; -"CreateChannelNamePlaceholder" = "Channel Name"; +"CreateChannelNamePlaceholder" = "Nombre del canal"; "CreateGroupMembersTitle" = "Invitar a los miembros"; @@ -680,48 +680,48 @@ "ActionAddPhoto2" = "foto"; -"ActionOpenCode" = "View Code"; +"ActionOpenCode" = "Ver código”; -"ActionMute" = "Mute"; +"ActionMute" = "Silenciar"; -"ActionUnmute" = "Unmute"; +"ActionUnmute" = "No silenciado"; -"ActionDelete" = "Delete"; +"ActionDelete" = "Eliminar"; -"ActionDeleteMessage" = "Are you sure want to delete chat?"; +"ActionDeleteMessage" = "¿Seguro desea borrar el chat?"; -"ActionDeleteChannel" = "Delete Channel"; +"ActionDeleteChannel" = "Eliminar canal"; "ActionDeleteChannelMessage" = "Wait! Deleting this channel will remove all members and all messages will be lost. Delete the channel anyway?"; "ActionDeleteGroup" = "Delete Group"; -"ActionDeleteGroupMessage" = "Wait! Deleting this group will remove all members and all messages will be lost. Delete the group anyway?"; +"ActionDeleteGroupMessage" = "¡Espere! La eliminación de este canal, eliminará todos los miembros y se perderán todos los mensajes. Eliminar el canal de todos modos?"; -"ActionLeaveChannel" = "Leave Channel"; +"ActionLeaveChannel" = "Abandonar Canal"; -"ActionLeaveChannelMessage" = "Are you sure want to leave channel?"; +"ActionLeaveChannelMessage" = "¿Seguro que quieres abandonar el canal?"; -"ActionLeaveChannelAction" = "Leave"; +"ActionLeaveChannelAction" = "Abandonar"; -"ActionDeleteAndExit" = "Delete and Exit"; +"ActionDeleteAndExit" = "Eliminar y Salir"; -"ActionDeleteAndExitMessage" = "Are you sure want to exit group and delete all messages?"; +"ActionDeleteAndExitMessage" = "¿Seguro desea salir del grupo y eliminar todos los mensajes?"; -"ActionDeleteAndExitAction" = "Exit"; +"ActionDeleteAndExitAction" = "Salir"; -"ActionClearHistory" = "Clear History"; +"ActionClearHistory" = "Borrar historial"; -"ActionClearHistoryMessage" = "Are you sure want to clear history?"; +"ActionClearHistoryMessage" = "¿Seguro desea borrar el historial?”; -"ActionClearHistoryAction" = "Clear"; +"ActionClearHistoryAction" = "Borrar"; /* * Network @@ -741,4 +741,4 @@ "ErrorAlreadyJoined" = "Ya eres miembro del grupo"; -"ErrorUnableToJoin" = "Unable to join to group"; +"ErrorUnableToJoin" = "No es posible unirse al grupo"; \ No newline at end of file From 1b5de13b42c501f844dd31d35d033c9f7514796e Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Fri, 29 Jul 2016 15:42:48 +0300 Subject: [PATCH 206/414] fix(iOS): Fixing compilation error --- .../ActorSDK/Resources/es.lproj/Localizable.strings | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/es.lproj/Localizable.strings b/actor-sdk/sdk-core-ios/ActorSDK/Resources/es.lproj/Localizable.strings index 607ff42e44..99f7668abb 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Resources/es.lproj/Localizable.strings +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/es.lproj/Localizable.strings @@ -297,15 +297,15 @@ "GroupTypeTitleChannel" = "Tipo de Canal"; -"GroupTypePublic" = "Público”; +"GroupTypePublic" = "Público"; -"GroupTypePrivate" = "Privado”; +"GroupTypePrivate" = "Privado"; -"ChannelTypePublic" = "Público”; +"ChannelTypePublic" = "Público"; -"ChannelTypePrivate" = "Privado”; +"ChannelTypePrivate" = "Privado"; -"GroupTypePublicFull" = "Grupo público“; +"GroupTypePublicFull" = "Grupo público"; "GroupTypePrivateFull" = "Grupo privado"; @@ -335,7 +335,7 @@ -"GroupShareTitle" = "Historial compartido”; +"GroupShareTitle" = "Historial compartido"; "GroupShareEnabled" = "Compartir"; From 15f4ed2eb31c02702098691e7b93042b07a9fe78 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Fri, 29 Jul 2016 15:44:15 +0300 Subject: [PATCH 207/414] fix(iOS): Fixing compilation error --- .../Resources/es.lproj/Localizable.strings | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/es.lproj/Localizable.strings b/actor-sdk/sdk-core-ios/ActorSDK/Resources/es.lproj/Localizable.strings index 99f7668abb..97d761b03c 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Resources/es.lproj/Localizable.strings +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/es.lproj/Localizable.strings @@ -287,11 +287,11 @@ "GroupViewMembers" = "Miembros"; -"GroupDescription" = "Descripción”; +"GroupDescription" = "Descripción"; -"GroupAdministration" = "Administrador”; +"GroupAdministration" = "Administrador"; "GroupTypeTitle" = "Tipo de Grupo"; @@ -346,15 +346,15 @@ "GroupShareAction" = "Compartir"; -"GroupEditTitle" = "Editar Grupo“; +"GroupEditTitle" = "Editar Grupo"; -"GroupEditTitleChannel" = "Editar Canal”; +"GroupEditTitleChannel" = "Editar Canal"; "GroupEditName" = "Nombre de Grupo"; "GroupEditNameChannel" = "Nombre del Canal"; -"GroupEditDescription" = "Descripción”; +"GroupEditDescription" = "Descripción"; @@ -413,14 +413,14 @@ "CreateGroup" = "Crear grupo"; -"CreateChannel" = "Crear canal”; +"CreateChannel" = "Crear canal"; "CreateGroupTitle" = "Crear grupo"; "CreateGroupHint" = "Por favor, introduce un nombre de grupo e inserta una imagen."; -"CreateGroupNamePlaceholder" = "Asunto del Grupo”; +"CreateGroupNamePlaceholder" = "Asunto del Grupo"; "CreateChannelTitle" = "Crear Canal“; @@ -680,7 +680,7 @@ "ActionAddPhoto2" = "foto"; -"ActionOpenCode" = "Ver código”; +"ActionOpenCode" = "Ver código"; "ActionMute" = "Silenciar"; @@ -719,7 +719,7 @@ "ActionClearHistory" = "Borrar historial"; -"ActionClearHistoryMessage" = "¿Seguro desea borrar el historial?”; +"ActionClearHistoryMessage" = "¿Seguro desea borrar el historial?"; "ActionClearHistoryAction" = "Borrar"; From 8e03a8fc92b86007fe9bbb5873a03e3e67705924 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Fri, 29 Jul 2016 15:45:46 +0300 Subject: [PATCH 208/414] fix(iOS): Fixing compilation error --- .../ActorSDK/Resources/es.lproj/Localizable.strings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/es.lproj/Localizable.strings b/actor-sdk/sdk-core-ios/ActorSDK/Resources/es.lproj/Localizable.strings index 97d761b03c..c405b02364 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Resources/es.lproj/Localizable.strings +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/es.lproj/Localizable.strings @@ -422,7 +422,7 @@ "CreateGroupNamePlaceholder" = "Asunto del Grupo"; -"CreateChannelTitle" = "Crear Canal“; +"CreateChannelTitle" = "Crear Canal"; "CreateChannelHint" = "Por favor, proporcione el nombre del canal y un icono de canal opcional"; From 75da8ab6f76cb01b000249e4da18113d07d89d39 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Fri, 29 Jul 2016 15:56:00 +0300 Subject: [PATCH 209/414] chore(android): leave and exit group option --- .../src/main/java/im/actor/core/Messenger.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java index 73c3f9a40c..97144815f0 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java @@ -28,6 +28,7 @@ import im.actor.core.entity.Peer; import im.actor.core.entity.PeerSearchEntity; import im.actor.core.entity.PeerSearchType; +import im.actor.core.entity.PeerType; import im.actor.core.entity.SearchResult; import im.actor.core.entity.Sex; import im.actor.core.entity.User; @@ -974,9 +975,15 @@ public void deleteMessages(Peer peer, long[] rids) { */ @ObjectiveCName("deleteChatCommandWithPeer:") public Command deleteChat(Peer peer) { - return callback -> modules.getMessagesModule().deleteChat(peer) - .then(v -> callback.onResult(v)) - .failure(e -> callback.onError(e)); + if (peer.getPeerType() == PeerType.GROUP) { + return callback -> modules.getGroupsModule().leaveAndDeleteGroup(peer.getPeerId()) + .then(v -> callback.onResult(v)) + .failure(e -> callback.onError(e)); + } else { + return callback -> modules.getMessagesModule().deleteChat(peer) + .then(v -> callback.onResult(v)) + .failure(e -> callback.onError(e)); + } } /** From 1090c001415fb888c7594e0f259c3cd05abef764 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Fri, 29 Jul 2016 16:18:56 +0300 Subject: [PATCH 210/414] feat(core): Updated scheme --- .../main/java/im/actor/core/api/ApiAdminSettings.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiAdminSettings.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiAdminSettings.java index 00aced48a1..b8ee0de80d 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiAdminSettings.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiAdminSettings.java @@ -20,12 +20,14 @@ public class ApiAdminSettings extends BserObject { private boolean canMembersInvite; private boolean canMembersEditGroupInfo; private boolean canAdminsEditGroupInfo; + private boolean showJoinLeaveMessages; - public ApiAdminSettings(boolean showAdminsToMembers, boolean canMembersInvite, boolean canMembersEditGroupInfo, boolean canAdminsEditGroupInfo) { + public ApiAdminSettings(boolean showAdminsToMembers, boolean canMembersInvite, boolean canMembersEditGroupInfo, boolean canAdminsEditGroupInfo, boolean showJoinLeaveMessages) { this.showAdminsToMembers = showAdminsToMembers; this.canMembersInvite = canMembersInvite; this.canMembersEditGroupInfo = canMembersEditGroupInfo; this.canAdminsEditGroupInfo = canAdminsEditGroupInfo; + this.showJoinLeaveMessages = showJoinLeaveMessages; } public ApiAdminSettings() { @@ -48,12 +50,17 @@ public boolean canAdminsEditGroupInfo() { return this.canAdminsEditGroupInfo; } + public boolean showJoinLeaveMessages() { + return this.showJoinLeaveMessages; + } + @Override public void parse(BserValues values) throws IOException { this.showAdminsToMembers = values.getBool(1); this.canMembersInvite = values.getBool(2); this.canMembersEditGroupInfo = values.getBool(3); this.canAdminsEditGroupInfo = values.getBool(4); + this.showJoinLeaveMessages = values.getBool(5); if (values.hasRemaining()) { setUnmappedObjects(values.buildRemaining()); } @@ -65,6 +72,7 @@ public void serialize(BserWriter writer) throws IOException { writer.writeBool(2, this.canMembersInvite); writer.writeBool(3, this.canMembersEditGroupInfo); writer.writeBool(4, this.canAdminsEditGroupInfo); + writer.writeBool(5, this.showJoinLeaveMessages); if (this.getUnmappedObjects() != null) { SparseArray unmapped = this.getUnmappedObjects(); for (int i = 0; i < unmapped.size(); i++) { @@ -81,6 +89,7 @@ public String toString() { res += ", canMembersInvite=" + this.canMembersInvite; res += ", canMembersEditGroupInfo=" + this.canMembersEditGroupInfo; res += ", canAdminsEditGroupInfo=" + this.canAdminsEditGroupInfo; + res += ", showJoinLeaveMessages=" + this.showJoinLeaveMessages; res += "}"; return res; } From 51b85e440d256ee11983a504c73949c6aa454475 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Fri, 29 Jul 2016 16:19:10 +0300 Subject: [PATCH 211/414] feat(core): Updated permissions --- .../actor/core/entity/GroupPermissions.java | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/GroupPermissions.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/GroupPermissions.java index 9c11d4ca6c..cc0b5fff66 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/GroupPermissions.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/GroupPermissions.java @@ -25,7 +25,26 @@ public void setMembersCanEditInfo(boolean canEditInfo) { settings.showAdminsToMembers(), settings.canMembersInvite(), canEditInfo, - settings.canAdminsEditGroupInfo() + settings.canAdminsEditGroupInfo(), + settings.showJoinLeaveMessages() + ); + settings.setUnmappedObjects(unmapped); + } + + @ObjectiveCName("isShowJoinLeaveMessages") + public boolean isShowJoinLeaveMessages() { + return settings.showJoinLeaveMessages(); + } + + @ObjectiveCName("setShowJoinLeaveMessages:") + public void setShowJoinLeaveMessages(boolean showJoinLeaveMessages) { + SparseArray unmapped = settings.getUnmappedObjects(); + settings = new ApiAdminSettings( + settings.showAdminsToMembers(), + settings.canMembersInvite(), + settings.canMembersEditGroupInfo(), + settings.canAdminsEditGroupInfo(), + showJoinLeaveMessages ); settings.setUnmappedObjects(unmapped); } From b555f25e0634a1898047ab53aa9068a610007215 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Fri, 29 Jul 2016 16:30:45 +0300 Subject: [PATCH 212/414] chore(android): leave and exit group option --- .../dialogs/DialogsDefaultFragment.java | 60 +++++++------------ .../main/java/im/actor/core/Messenger.java | 12 +--- 2 files changed, 26 insertions(+), 46 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/DialogsDefaultFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/DialogsDefaultFragment.java index 205a0a74ff..8663b3fa9b 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/DialogsDefaultFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/DialogsDefaultFragment.java @@ -69,50 +69,36 @@ public void onError(Exception e) { return true; } else if (dialog.getPeer().getPeerType() == PeerType.GROUP) { GroupVM groupVM = groups().get(dialog.getPeer().getPeerId()); - final boolean isMember = groupVM.isMember().get(); + CharSequence[] items; + if (groupVM.getIsCanLeave().get()) { + items = new CharSequence[]{ + getString(R.string.dialogs_menu_group_view), + getString(R.string.dialogs_menu_group_rename), + getString(R.string.dialogs_menu_group_leave), + }; + } else { + items = new CharSequence[]{ + getString(R.string.dialogs_menu_group_view), + getString(R.string.dialogs_menu_group_rename), + }; + } new AlertDialog.Builder(getActivity()) - .setItems(new CharSequence[]{ - getString(R.string.dialogs_menu_group_view), - getString(R.string.dialogs_menu_group_rename), - isMember ? getString(R.string.dialogs_menu_group_leave) - : getString(R.string.dialogs_menu_group_delete), - }, (d, which) -> { + .setItems(items, (d, which) -> { if (which == 0) { ActorSDK.sharedActor().startGroupInfoActivity(getActivity(), dialog.getPeer().getPeerId()); } else if (which == 1) { startActivity(Intents.editGroupTitle(dialog.getPeer().getPeerId(), getActivity())); } else if (which == 2) { - if (isMember) { - new AlertDialog.Builder(getActivity()) - .setMessage(getString(R.string.alert_leave_group_message, dialog.getDialogTitle())) - .setNegativeButton(R.string.dialog_cancel, null) - .setPositiveButton(R.string.alert_leave_group_yes, (d1, which1) -> { - execute(messenger().leaveGroup(dialog.getPeer().getPeerId()), R.string.progress_common).failure(e -> { - Toast.makeText(getActivity(), R.string.toast_unable_leave, Toast.LENGTH_LONG).show(); - }); - }) - .show(); - } else { - new AlertDialog.Builder(getActivity()) - .setMessage(getString(R.string.alert_delete_group_title, groupVM.getName().get())) - .setNegativeButton(R.string.dialog_cancel, null) - .setPositiveButton(R.string.alert_delete_group_yes, (d1, which1) -> { - execute(messenger().deleteChat(dialog.getPeer()), R.string.progress_common, - new CommandCallback() { - @Override - public void onResult(Void res) { - - } - - @Override - public void onError(Exception e) { - Toast.makeText(getActivity(), R.string.toast_unable_delete_chat, Toast.LENGTH_LONG).show(); - } - }); - }) - .show(); - } + new AlertDialog.Builder(getActivity()) + .setMessage(getString(R.string.alert_leave_group_message, dialog.getDialogTitle())) + .setNegativeButton(R.string.dialog_cancel, null) + .setPositiveButton(R.string.alert_leave_group_yes, (d1, which1) -> { + execute(messenger().leaveAndDeleteGroup(dialog.getPeer().getPeerId()), R.string.progress_common).failure(e -> { + Toast.makeText(getActivity(), R.string.toast_unable_leave, Toast.LENGTH_LONG).show(); + }); + }) + .show(); } }).show(); return true; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java index 97144815f0..b551eb3044 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java @@ -975,15 +975,9 @@ public void deleteMessages(Peer peer, long[] rids) { */ @ObjectiveCName("deleteChatCommandWithPeer:") public Command deleteChat(Peer peer) { - if (peer.getPeerType() == PeerType.GROUP) { - return callback -> modules.getGroupsModule().leaveAndDeleteGroup(peer.getPeerId()) - .then(v -> callback.onResult(v)) - .failure(e -> callback.onError(e)); - } else { - return callback -> modules.getMessagesModule().deleteChat(peer) - .then(v -> callback.onResult(v)) - .failure(e -> callback.onError(e)); - } + return callback -> modules.getMessagesModule().deleteChat(peer) + .then(v -> callback.onResult(v)) + .failure(e -> callback.onError(e)); } /** From 20da6821f5151ad5ed1d1635dd70127bfeed5f1c Mon Sep 17 00:00:00 2001 From: kor_ka Date: Fri, 29 Jul 2016 17:38:43 +0300 Subject: [PATCH 213/414] fix(android): move to new groups - bump STORAGE_SCHEME_VERSION --- .../java/im/actor/core/modules/storage/StorageModule.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/storage/StorageModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/storage/StorageModule.java index 405509ee39..40ede34306 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/storage/StorageModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/storage/StorageModule.java @@ -12,8 +12,8 @@ public class StorageModule extends AbsModule { private static String TAG = "StorageModule"; - private static final int STORAGE_SCHEME_VERSION = 13; - private static final String STORAGE_SCHEME_VERSION_KEY = "storage_sheme_version"; + private static final int STORAGE_SCHEME_VERSION = 14; + private static final String STORAGE_SCHEME_VERSION_KEY = "storage_scheme_version"; private KeyValueStorage storage; From 4486d40bb2ecbdc5597137b59c55cae5ce3f556a Mon Sep 17 00:00:00 2001 From: kor_ka Date: Mon, 1 Aug 2016 14:39:01 +0300 Subject: [PATCH 214/414] fix(android): close search after global search item selected --- .../actor/sdk/controllers/search/GlobalSearchBaseFragment.java | 1 + 1 file changed, 1 insertion(+) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/search/GlobalSearchBaseFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/search/GlobalSearchBaseFragment.java index bb58b99b2a..e0fa1ca495 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/search/GlobalSearchBaseFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/search/GlobalSearchBaseFragment.java @@ -274,6 +274,7 @@ public boolean onLongClicked(SearchEntity item) { footerSearchHolder = new SearchHolder(getActivity(), new OnItemClickedListener() { @Override public void onClicked(SearchEntity item) { + searchMenu.collapseActionView(); int peerId = item.getPeer().getPeerId(); switch (item.getPeer().getPeerType()) { case PRIVATE: From 9f704e7baada4d627ffda9a4be1a2f2014805a39 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Mon, 1 Aug 2016 14:52:19 +0300 Subject: [PATCH 215/414] fix(android): delete group if no leave option --- .../dialogs/DialogsDefaultFragment.java | 44 ++++++++++++------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/DialogsDefaultFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/DialogsDefaultFragment.java index 8663b3fa9b..4b36a208bd 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/DialogsDefaultFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/DialogsDefaultFragment.java @@ -70,19 +70,13 @@ public void onError(Exception e) { } else if (dialog.getPeer().getPeerType() == PeerType.GROUP) { GroupVM groupVM = groups().get(dialog.getPeer().getPeerId()); CharSequence[] items; - if (groupVM.getIsCanLeave().get()) { - items = new CharSequence[]{ - getString(R.string.dialogs_menu_group_view), - getString(R.string.dialogs_menu_group_rename), - getString(R.string.dialogs_menu_group_leave), - }; - } else { - items = new CharSequence[]{ - getString(R.string.dialogs_menu_group_view), - getString(R.string.dialogs_menu_group_rename), - }; - } - + items = new CharSequence[]{ + getString(R.string.dialogs_menu_group_view), + getString(R.string.dialogs_menu_group_rename), + getString(groupVM.getIsCanLeave().get() ? R.string.dialogs_menu_group_leave : + groupVM.getIsCanDelete().get() ? R.string.dialogs_menu_group_delete : + R.string.dialogs_menu_group_delete), + }; new AlertDialog.Builder(getActivity()) .setItems(items, (d, which) -> { if (which == 0) { @@ -94,9 +88,27 @@ public void onError(Exception e) { .setMessage(getString(R.string.alert_leave_group_message, dialog.getDialogTitle())) .setNegativeButton(R.string.dialog_cancel, null) .setPositiveButton(R.string.alert_leave_group_yes, (d1, which1) -> { - execute(messenger().leaveAndDeleteGroup(dialog.getPeer().getPeerId()), R.string.progress_common).failure(e -> { - Toast.makeText(getActivity(), R.string.toast_unable_leave, Toast.LENGTH_LONG).show(); - }); + if (groupVM.getIsCanLeave().get()) { + execute(messenger().leaveAndDeleteGroup(dialog.getPeer().getPeerId()), R.string.progress_common).failure(e -> { + Toast.makeText(getActivity(), R.string.toast_unable_leave, Toast.LENGTH_LONG).show(); + }); + } else if (groupVM.getIsCanDelete().get()) { + execute(messenger().deleteGroup(dialog.getPeer().getPeerId()), R.string.progress_common).failure(e -> { + Toast.makeText(getActivity(), R.string.toast_unable_delete_chat, Toast.LENGTH_LONG).show(); + }); + } else { + execute(messenger().deleteChat(dialog.getPeer()), R.string.progress_common, new CommandCallback() { + @Override + public void onResult(Void res) { + + } + + @Override + public void onError(Exception e) { + Toast.makeText(getActivity(), R.string.toast_unable_delete_chat, Toast.LENGTH_LONG).show(); + } + }); + } }) .show(); } From 6856c7724d7faa01698e376b4e67d1fa584561ed Mon Sep 17 00:00:00 2001 From: kor_ka Date: Mon, 1 Aug 2016 16:29:57 +0300 Subject: [PATCH 216/414] fix(android): delete group in administration fragment confirm --- .../dialogs/DialogsDefaultFragment.java | 4 +- .../controllers/group/GroupAdminFragment.java | 38 +++++++++++++++---- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/DialogsDefaultFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/DialogsDefaultFragment.java index 4b36a208bd..572471b46c 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/DialogsDefaultFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/DialogsDefaultFragment.java @@ -85,7 +85,9 @@ public void onError(Exception e) { startActivity(Intents.editGroupTitle(dialog.getPeer().getPeerId(), getActivity())); } else if (which == 2) { new AlertDialog.Builder(getActivity()) - .setMessage(getString(R.string.alert_leave_group_message, dialog.getDialogTitle())) + .setMessage(getString(groupVM.getIsCanLeave().get() ? R.string.alert_delete_group_title : + groupVM.getIsCanDelete().get() ? R.string.alert_delete_group_title : + R.string.alert_leave_group_message, dialog.getDialogTitle())) .setNegativeButton(R.string.dialog_cancel, null) .setPositiveButton(R.string.alert_leave_group_yes, (d1, which1) -> { if (groupVM.getIsCanLeave().get()) { diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupAdminFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupAdminFragment.java index 6c9d14477e..c37527e65f 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupAdminFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupAdminFragment.java @@ -3,13 +3,20 @@ import android.content.Intent; import android.os.Bundle; import android.support.annotation.Nullable; +import android.support.v7.app.AlertDialog; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; +import android.widget.Toast; import im.actor.core.entity.GroupType; +import im.actor.core.entity.Peer; +import im.actor.core.viewmodel.CommandCallback; import im.actor.core.viewmodel.GroupVM; +import im.actor.runtime.actors.messages.*; +import im.actor.runtime.actors.messages.Void; +import im.actor.runtime.mvvm.ValueDoubleListener; import im.actor.sdk.ActorSDK; import im.actor.sdk.R; import im.actor.sdk.controllers.BaseFragment; @@ -136,23 +143,40 @@ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, delete.setText(R.string.group_delete); deleteHint.setText(R.string.group_delete_hint); } - bind(groupVM.getIsCanDelete(), canDelete -> { - if (canDelete) { + + bind(groupVM.getIsCanLeave(), groupVM.getIsCanDelete(), (canLeave, canDelete) -> { + if (canDelete || canDelete) { deleteContainer.setVisibility(View.VISIBLE); delete.setOnClickListener(v -> { - execute(messenger().deleteGroup(groupVM.getId())).then(r -> { - ActorSDK.sharedActor().startMessagingApp(getActivity()); - finishActivity(); - }); + + new AlertDialog.Builder(getActivity()) + .setMessage(getString(groupVM.getIsCanLeave().get() ? R.string.alert_delete_group_title : + groupVM.getIsCanDelete().get() ? R.string.alert_delete_group_title : + R.string.alert_leave_group_message, groupVM.getName().get())) + .setNegativeButton(R.string.dialog_cancel, null) + .setPositiveButton(R.string.alert_delete_group_yes, (d1, which1) -> { + if (groupVM.getIsCanLeave().get()) { + execute(messenger().leaveAndDeleteGroup(groupVM.getId()), R.string.progress_common).failure(e -> { + Toast.makeText(getActivity(), R.string.toast_unable_leave, Toast.LENGTH_LONG).show(); + }); + } else if (groupVM.getIsCanDelete().get()) { + execute(messenger().deleteGroup(groupVM.getId()), R.string.progress_common).failure(e -> { + Toast.makeText(getActivity(), R.string.toast_unable_delete_chat, Toast.LENGTH_LONG).show(); + }); + } + }) + .show(); + }); + } else { deleteContainer.setVisibility(View.GONE); delete.setOnClickListener(null); delete.setClickable(false); } - }); + return res; } } From e28a5085c407c902496d743d875dd89eb8970d01 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Mon, 1 Aug 2016 20:09:21 +0300 Subject: [PATCH 217/414] chore(android): ru translations --- .../src/main/res/values-ru/ui_text.xml | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-ru/ui_text.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-ru/ui_text.xml index 628585ad26..4ee05b2e9b 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-ru/ui_text.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-ru/ui_text.xml @@ -19,6 +19,7 @@ Загрузка Загрузка… + Смена ника… Нет SD-карты Телефон скопирован в буфер обмена @@ -77,6 +78,9 @@ Использовать email для регистрации Использовать телефон для регистрации + Политика Конфиденциальности + Условия Использования + Выбор способа авторизации Пожалуйста, выберите способ авторизации. @@ -190,6 +194,18 @@ О пользователе Обои + + добвить в разговор + Соединяем… + тихо + громкая связь + видео + Вызов окончен + Входящий вызов + Вернуться к вызову + Звоним… + сообщения + Позвонить {0} Отправить письмо {0} @@ -274,6 +290,9 @@ Вы можете редактировать только последнее сообщение Вы можете редактировать только недавно отправленные сообщения + СТАРТ + Войти + Галерея Видео @@ -351,6 +370,40 @@ Добавить описание группы Тема группы Добавить тему группы + + Администрирование группы + Администрирование + Все пользователи могут редактировать информацию о группе + Удалить группу + Вы потеряете все сообщения в этой группе + Описание + Редактировать + Имя + Разрешения + Управление правами пользователей в этой группе + В приватную группу можно попасть только по личному приглашению + Приватная группа + Публичные группы доступны для поиска, кто угодно можнет вступить в такую группу + Публичная группа + Поделиться историей + Все сообщения будут доступны всем участникам + Тип группы + Частная + Публичная + + + Администрирование канала + Все пользователи могут редактировать иформацию о канале + Удалить канал + Вы потеряете все сообщения в этом канале + Управление разрешениями в этом канале + Тип канала + + Введите имя канала и выбирете изображение + Название канала + Создать канал + Создать канал + Ваше фото Фото @@ -462,6 +515,10 @@ Только для упоминаний Включить оповещения в группах только для сообщений с упоминанием + " сообщений в " + " чатах" + " сообщений" + Выберите человека @@ -541,5 +598,13 @@ Невозможно произвести операцию.\n\nПожалуйста, проверьте свой телефон и выключите и включите его, если проблема не будет решена. Неизвестный тип ссылки + + Невозможно поменять ник + Невозможно очистить чат + Невозможно создать группу + Невозможно удалить чат + Невозможно покинуть группу + Поделиться с + From bbd387e05949c5e861d452122f8b2df97ddb9892 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Mon, 1 Aug 2016 22:31:25 +0300 Subject: [PATCH 218/414] chore(android): hackable chat fragment --- .../src/main/java/im/actor/sdk/ActorSDKDelegate.java | 10 ++++++++++ .../main/java/im/actor/sdk/BaseActorSDKDelegate.java | 7 +++++++ .../sdk/controllers/conversation/ChatActivity.java | 5 ++++- 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorSDKDelegate.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorSDKDelegate.java index 2f964b73e5..00845b5835 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorSDKDelegate.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorSDKDelegate.java @@ -8,6 +8,7 @@ import im.actor.core.entity.Peer; import im.actor.runtime.android.view.BindedViewHolder; +import im.actor.sdk.controllers.conversation.ChatFragment; import im.actor.sdk.controllers.conversation.attach.AbsAttachFragment; import im.actor.sdk.controllers.conversation.messages.content.MessageHolder; import im.actor.sdk.controllers.conversation.messages.MessagesAdapter; @@ -79,6 +80,15 @@ public interface ActorSDKDelegate { @Nullable AbsAttachFragment fragmentForAttachMenu(Peer peer); + /** + * If Not null returned, overrides chat fragment + * + * @param peer peer + * @return Custom chat fragment + */ + @Nullable + ChatFragment fragmentForChat(Peer peer); + /** * If Not null returned, overrides default toolbar (no-ui) fragment * diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/BaseActorSDKDelegate.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/BaseActorSDKDelegate.java index 076aa505e2..4a08bb9c7e 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/BaseActorSDKDelegate.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/BaseActorSDKDelegate.java @@ -9,6 +9,7 @@ import im.actor.core.entity.Peer; import im.actor.runtime.android.view.BindedViewHolder; +import im.actor.sdk.controllers.conversation.ChatFragment; import im.actor.sdk.controllers.conversation.attach.AbsAttachFragment; import im.actor.sdk.controllers.conversation.messages.content.MessageHolder; import im.actor.sdk.controllers.conversation.messages.MessagesAdapter; @@ -60,6 +61,12 @@ public AbsAttachFragment fragmentForAttachMenu(Peer peer) { return null; } + @Nullable + @Override + public ChatFragment fragmentForChat(Peer peer) { + return null; + } + @Nullable @Override public Fragment fragmentForToolbar(Peer peer) { diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatActivity.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatActivity.java index 8c4f2fe3d5..4885efc48f 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatActivity.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatActivity.java @@ -96,7 +96,10 @@ public void onCreate(Bundle saveInstance) { if (saveInstance == null) { Peer peer = Peer.fromUniqueId(getIntent().getExtras().getLong(EXTRA_CHAT_PEER)); - chatFragment = ChatFragment.create(peer); + ChatFragment fragment = ActorSDK.sharedActor().getDelegate().fragmentForChat(peer); + if (fragment == null) { + chatFragment = ChatFragment.create(peer); + } getSupportFragmentManager().beginTransaction() .add(R.id.chatFragment, chatFragment) .commitNow(); From 1f3afe33d13e69c65c781aaef005446c723ed65c Mon Sep 17 00:00:00 2001 From: kor_ka Date: Mon, 1 Aug 2016 22:50:51 +0300 Subject: [PATCH 219/414] fix(android): hackable chat fragment --- .../im/actor/sdk/controllers/conversation/ChatActivity.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatActivity.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatActivity.java index 4885efc48f..e73d12e048 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatActivity.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatActivity.java @@ -96,8 +96,8 @@ public void onCreate(Bundle saveInstance) { if (saveInstance == null) { Peer peer = Peer.fromUniqueId(getIntent().getExtras().getLong(EXTRA_CHAT_PEER)); - ChatFragment fragment = ActorSDK.sharedActor().getDelegate().fragmentForChat(peer); - if (fragment == null) { + chatFragment = ActorSDK.sharedActor().getDelegate().fragmentForChat(peer); + if (chatFragment == null) { chatFragment = ChatFragment.create(peer); } getSupportFragmentManager().beginTransaction() From 0a8f45384b58a8a3ee51f540952e9e82bcc7ff9a Mon Sep 17 00:00:00 2001 From: kor_ka Date: Mon, 1 Aug 2016 23:13:06 +0300 Subject: [PATCH 220/414] chore(android): hackable chat input fragment --- .../src/main/java/im/actor/sdk/ActorSDKDelegate.java | 9 +++++++++ .../src/main/java/im/actor/sdk/BaseActorSDKDelegate.java | 7 +++++++ .../actor/sdk/controllers/conversation/ChatFragment.java | 6 +++++- 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorSDKDelegate.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorSDKDelegate.java index 00845b5835..a1fff73cea 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorSDKDelegate.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorSDKDelegate.java @@ -10,6 +10,7 @@ import im.actor.runtime.android.view.BindedViewHolder; import im.actor.sdk.controllers.conversation.ChatFragment; import im.actor.sdk.controllers.conversation.attach.AbsAttachFragment; +import im.actor.sdk.controllers.conversation.inputbar.InputBarFragment; import im.actor.sdk.controllers.conversation.messages.content.MessageHolder; import im.actor.sdk.controllers.conversation.messages.MessagesAdapter; import im.actor.sdk.controllers.settings.BaseGroupInfoActivity; @@ -89,6 +90,14 @@ public interface ActorSDKDelegate { @Nullable ChatFragment fragmentForChat(Peer peer); + /** + * If Not null returned, overrides chat input fragment + * + * @return Custom chat input fragment + */ + @Nullable + InputBarFragment fragmentForChatInput(); + /** * If Not null returned, overrides default toolbar (no-ui) fragment * diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/BaseActorSDKDelegate.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/BaseActorSDKDelegate.java index 4a08bb9c7e..8bb53d488b 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/BaseActorSDKDelegate.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/BaseActorSDKDelegate.java @@ -11,6 +11,7 @@ import im.actor.runtime.android.view.BindedViewHolder; import im.actor.sdk.controllers.conversation.ChatFragment; import im.actor.sdk.controllers.conversation.attach.AbsAttachFragment; +import im.actor.sdk.controllers.conversation.inputbar.InputBarFragment; import im.actor.sdk.controllers.conversation.messages.content.MessageHolder; import im.actor.sdk.controllers.conversation.messages.MessagesAdapter; import im.actor.sdk.controllers.settings.BaseGroupInfoActivity; @@ -67,6 +68,12 @@ public ChatFragment fragmentForChat(Peer peer) { return null; } + @Nullable + @Override + public InputBarFragment fragmentForChatInput() { + return null; + } + @Nullable @Override public Fragment fragmentForToolbar(Peer peer) { diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatFragment.java index 63eb29089f..e41470f3a6 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatFragment.java @@ -97,10 +97,14 @@ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, if (toolbarFragment == null) { toolbarFragment = new ChatToolbarFragment(peer); } + InputBarFragment inputBarFragment = ActorSDK.sharedActor().getDelegate().fragmentForChatInput(); + if (inputBarFragment == null) { + inputBarFragment = new InputBarFragment(); + } getChildFragmentManager().beginTransaction() .add(toolbarFragment, "toolbar") .add(R.id.messagesFragment, MessagesDefaultFragment.create(peer)) - .add(R.id.sendFragment, new InputBarFragment()) + .add(R.id.sendFragment, inputBarFragment) .add(R.id.quoteFragment, new QuoteFragment()) .add(R.id.emptyPlaceholder, new EmptyChatPlaceholder()) .add(R.id.autocompleteContainer, new AutocompleteFragment(peer)) From f4ad8a8e4d910dbe5096cfbb0782b0b8860ec2c6 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Tue, 2 Aug 2016 00:28:54 +0300 Subject: [PATCH 221/414] feat(core+android): Loading Appp in Staging, auto-joining to groups --- .../src/main/java/im/actor/Application.java | 4 + .../src/main/java/im/actor/sdk/ActorSDK.java | 51 ++++ .../java/im/actor/core/AndroidMessenger.java | 2 +- .../main/java/im/actor/core/AutoJoinType.java | 6 + .../java/im/actor/core/Configuration.java | 28 +- .../im/actor/core/ConfigurationBuilder.java | 33 ++- .../main/java/im/actor/core/Messenger.java | 4 +- .../im/actor/core/modules/ModuleContext.java | 11 +- .../java/im/actor/core/modules/Modules.java | 38 +-- .../core/modules/conductor/Conductor.java | 37 +++ .../modules/conductor/ConductorActor.java | 260 ++++++++++++++++++ .../modules/conductor/ConductorModule.java | 36 +++ .../{misc => conductor}/DisplayLists.java | 2 +- .../modules/contacts/BookImportActor.java | 23 +- .../core/modules/contacts/ContactsModule.java | 4 + .../modules/contacts/ContactsSyncActor.java | 4 +- .../messaging/dialogs/DialogsActor.java | 2 +- .../history/DialogsHistoryActor.java | 6 +- .../modules/messaging/router/RouterActor.java | 4 +- .../core/modules/misc/AppStateModule.java | 58 ---- .../core/modules/misc/DeviceInfoActor.java | 68 ----- .../core/modules/misc/DeviceInfoModule.java | 23 -- .../core/modules/sequence/SequenceActor.java | 4 +- .../core/modules/settings/SettingsModule.java | 4 + .../modules/settings/SettingsProcessor.java | 1 + .../modules/settings/SettingsSyncActor.java | 4 + .../im/actor/core/viewmodel/AppStateVM.java | 50 +++- .../actor/core/viewmodel/GlobalStateVM.java | 24 +- 28 files changed, 576 insertions(+), 215 deletions(-) create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/AutoJoinType.java create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/conductor/Conductor.java create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/conductor/ConductorActor.java create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/conductor/ConductorModule.java rename actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/{misc => conductor}/DisplayLists.java (98%) delete mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/misc/AppStateModule.java delete mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/misc/DeviceInfoActor.java delete mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/misc/DeviceInfoModule.java diff --git a/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/Application.java b/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/Application.java index f032f75f34..9f7f8be97a 100644 --- a/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/Application.java +++ b/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/Application.java @@ -57,6 +57,10 @@ public void onConfigureActorSDK() { ActorSDK.sharedActor().setVideoCallsEnabled(true); + ActorSDK.sharedActor().setAutoJoinGroups(new String[]{ + "actor_news" + }); + // ActorSDK.sharedActor().setTwitter(""); // ActorSDK.sharedActor().setHomePage("http://www.foo.com"); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorSDK.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorSDK.java index 17f3a902cf..1ed3dc8afd 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorSDK.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorSDK.java @@ -21,6 +21,7 @@ import im.actor.core.AndroidMessenger; import im.actor.core.ApiConfiguration; +import im.actor.core.AutoJoinType; import im.actor.core.ConfigurationBuilder; import im.actor.core.DeviceCategory; import im.actor.core.PlatformType; @@ -170,6 +171,11 @@ public class ActorSDK { */ private boolean fastShareEnabled = false; + /** + * Auto Join Groups + */ + private String[] autoJoinGroups = new String[0]; + private AutoJoinType autoJoinType = AutoJoinType.AFTER_INIT; /** * Auth type - binary mask for auth type @@ -291,6 +297,14 @@ public void createActor(final Application application) { // builder.setCallsProvider(new AndroidCallProvider()); + // + // Auto Join + // + for (String s : autoJoinGroups) { + builder.addAutoJoinGroup(s); + } + builder.setAutoJoinType(autoJoinType); + // // Building Messenger // @@ -787,6 +801,43 @@ public void setPrivacyText(String privacyText) { this.privacyText = privacyText; } + + /** + * Get Current Auto Join group tokens + * + * @return auto join tokens + */ + public String[] getAutoJoinGroups() { + return autoJoinGroups; + } + + /** + * Set Auto Join group tokens + * + * @param autoJoinGroups auto join tokens + */ + public void setAutoJoinGroups(String[] autoJoinGroups) { + this.autoJoinGroups = autoJoinGroups; + } + + /** + * Set auto join type + * + * @return auto join type + */ + public AutoJoinType getAutoJoinType() { + return autoJoinType; + } + + /** + * Set auto join type + * + * @param autoJoinType auto join type + */ + public void setAutoJoinType(AutoJoinType autoJoinType) { + this.autoJoinType = autoJoinType; + } + /** * Setting Application Delegate. Useful for hacking various parts of SDK * diff --git a/actor-sdk/sdk-core/core/core-android/src/main/java/im/actor/core/AndroidMessenger.java b/actor-sdk/sdk-core/core/core-android/src/main/java/im/actor/core/AndroidMessenger.java index 6ddf47cf36..e169abe598 100644 --- a/actor-sdk/sdk-core/core/core-android/src/main/java/im/actor/core/AndroidMessenger.java +++ b/actor-sdk/sdk-core/core/core-android/src/main/java/im/actor/core/AndroidMessenger.java @@ -91,7 +91,7 @@ public void onChange(boolean selfChange) { })); // Counters - modules.getAppStateModule() + modules.getConductor() .getGlobalStateVM() .getGlobalCounter() .subscribe((val, valueModel) -> { diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/AutoJoinType.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/AutoJoinType.java new file mode 100644 index 0000000000..1b7e81c47f --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/AutoJoinType.java @@ -0,0 +1,6 @@ +package im.actor.core; + +public enum AutoJoinType { + AFTER_INIT, + IMMEDIATELY +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Configuration.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Configuration.java index cc46e76e87..eb14a49ee3 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Configuration.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Configuration.java @@ -60,6 +60,10 @@ public class Configuration { private final CallsProvider callsProvider; @Property("readonly, nonatomic") private final boolean isEnabledGroupedChatList; + @Property("readonly, nonatomic") + private final String[] autoJoinGroups; + @Property("readonly, nonatomic") + private final AutoJoinType autoJoinType; Configuration(ConnectionEndpoint[] endpoints, PhoneBookProvider phoneBookProvider, @@ -81,7 +85,9 @@ public class Configuration { CallsProvider callsProvider, boolean voiceCallsEnabled, boolean videoCallsEnabled, - boolean isEnabledGroupedChatList) { + boolean isEnabledGroupedChatList, + String[] autoJoinGroups, + AutoJoinType autoJoinType) { this.endpoints = endpoints; this.phoneBookProvider = phoneBookProvider; this.enableContactsLogging = enableContactsLogging; @@ -103,6 +109,8 @@ public class Configuration { this.voiceCallsEnabled = voiceCallsEnabled; this.videoCallsEnabled = videoCallsEnabled; this.isEnabledGroupedChatList = isEnabledGroupedChatList; + this.autoJoinGroups = autoJoinGroups; + this.autoJoinType = autoJoinType; } /** @@ -293,4 +301,22 @@ public String[] getPreferredLanguages() { public boolean isEnabledGroupedChatList() { return isEnabledGroupedChatList; } + + /** + * Get Auto Join groups + * + * @return list of auto join groups + */ + public String[] getAutoJoinGroups() { + return autoJoinGroups; + } + + /** + * Get Auto Join Type + * + * @return auto join type + */ + public AutoJoinType getAutoJoinType() { + return autoJoinType; + } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/ConfigurationBuilder.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/ConfigurationBuilder.java index 6a9971074f..870c88492e 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/ConfigurationBuilder.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/ConfigurationBuilder.java @@ -62,6 +62,22 @@ public class ConfigurationBuilder { private boolean isEnabledGroupedChatList = true; + private ArrayList autoJoinGroups = new ArrayList<>(); + private AutoJoinType autoJoinType = AutoJoinType.AFTER_INIT; + + /** + * Setting Auto Join to group type: when to join to your groups + * + * @param autoJoinType auto join type + * @return this + */ + @ObjectiveCName("setAutoJoinType:") + public ConfigurationBuilder setAutoJoinType(AutoJoinType autoJoinType) { + this.autoJoinType = autoJoinType; + return this; + } + + /** * Setting if grouped chat list support enabled * @@ -137,6 +153,19 @@ public ConfigurationBuilder addTrustedKey(String trustedKey) { return this; } + /** + * Adding group to auto join of users + * + * @param groupTokenOrShortName group's token or short name + * @return this + */ + @NotNull + @ObjectiveCName("addAutoJoinGroupWithToken:") + public ConfigurationBuilder addAutoJoinGroup(String groupTokenOrShortName) { + autoJoinGroups.add(groupTokenOrShortName); + return this; + } + /** * Setting custom application name * @@ -419,6 +448,8 @@ public Configuration build() { callsProvider, voiceCallsEnabled, videoCallsEnabled, - isEnabledGroupedChatList); + isEnabledGroupedChatList, + autoJoinGroups.toArray(new String[autoJoinGroups.size()]), + autoJoinType); } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java index b551eb3044..748047474c 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java @@ -399,7 +399,7 @@ public void onLoggedIn() { @NotNull @ObjectiveCName("getAppState") public AppStateVM getAppState() { - return modules.getAppStateModule().getAppStateVM(); + return modules.getConductor().getAppStateVM(); } /** @@ -410,7 +410,7 @@ public AppStateVM getAppState() { @NotNull @ObjectiveCName("getGlobalState") public GlobalStateVM getGlobalState() { - return modules.getAppStateModule().getGlobalStateVM(); + return modules.getConductor().getGlobalStateVM(); } /** diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/ModuleContext.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/ModuleContext.java index bc1b76d054..8033cc6bfc 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/ModuleContext.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/ModuleContext.java @@ -7,11 +7,9 @@ import im.actor.core.modules.auth.Authentication; import im.actor.core.modules.eventbus.EventBusModule; import im.actor.core.modules.sequence.Updates; -import im.actor.core.modules.misc.AppStateModule; import im.actor.core.modules.calls.CallsModule; import im.actor.core.modules.contacts.ContactsModule; -import im.actor.core.modules.misc.DeviceInfoModule; -import im.actor.core.modules.misc.DisplayLists; +import im.actor.core.modules.conductor.DisplayLists; import im.actor.core.modules.encryption.EncryptionModule; import im.actor.core.modules.external.ExternalModule; import im.actor.core.modules.file.FilesModule; @@ -29,6 +27,7 @@ import im.actor.core.modules.storage.StorageModule; import im.actor.core.modules.typing.TypingModule; import im.actor.core.modules.users.UsersModule; +import im.actor.core.modules.conductor.ConductorModule; import im.actor.core.network.ActorApi; import im.actor.runtime.eventbus.EventBus; import im.actor.runtime.storage.PreferencesStorage; @@ -84,8 +83,6 @@ public interface ModuleContext { ProfileModule getProfileModule(); - AppStateModule getAppStateModule(); - PushesModule getPushesModule(); SecurityModule getSecurityModule(); @@ -100,9 +97,9 @@ public interface ModuleContext { MentionsModule getMentions(); - DeviceInfoModule getDeviceInfoModule(); - EncryptionModule getEncryption(); EventBusModule getEventBus(); + + ConductorModule getConductor(); } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/Modules.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/Modules.java index 618cefdb2f..ea51a103f6 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/Modules.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/Modules.java @@ -11,11 +11,9 @@ import im.actor.core.modules.auth.Authentication; import im.actor.core.modules.eventbus.EventBusModule; import im.actor.core.modules.sequence.Updates; -import im.actor.core.modules.misc.AppStateModule; import im.actor.core.modules.calls.CallsModule; import im.actor.core.modules.contacts.ContactsModule; -import im.actor.core.modules.misc.DeviceInfoModule; -import im.actor.core.modules.misc.DisplayLists; +import im.actor.core.modules.conductor.DisplayLists; import im.actor.core.modules.encryption.EncryptionModule; import im.actor.core.modules.external.ExternalModule; import im.actor.core.modules.file.FilesModule; @@ -33,6 +31,7 @@ import im.actor.core.modules.storage.StorageModule; import im.actor.core.modules.typing.TypingModule; import im.actor.core.modules.users.UsersModule; +import im.actor.core.modules.conductor.ConductorModule; import im.actor.core.network.ActorApi; import im.actor.core.util.Timing; import im.actor.runtime.Runtime; @@ -63,7 +62,6 @@ public class Modules implements ModuleContext { private volatile Updates updates; private volatile UsersModule users; private volatile GroupsModule groups; - private volatile AppStateModule appStateModule; private volatile StickersModule stickers; private volatile CallsModule calls; private volatile MessagesModule messages; @@ -80,7 +78,7 @@ public class Modules implements ModuleContext { private volatile DisplayLists displayLists; private volatile MentionsModule mentions; private volatile EncryptionModule encryptionModule; - private volatile DeviceInfoModule deviceInfoModule; + private volatile ConductorModule conductor; private volatile EventBusModule eventBusModule; public Modules(Messenger messenger, Configuration configuration) { @@ -101,8 +99,8 @@ public Modules(Messenger messenger, Configuration configuration) { // timing.section("Events"); this.events = new EventBus(); - // timing.section("App State"); - appStateModule = new AppStateModule(this); + // timing.section("Conductor"); + this.conductor = new ConductorModule(this); // timing.section("API"); this.api = new ApiModule(this); @@ -127,14 +125,15 @@ public void run() { public void onLoggedIn(boolean first) { Timing timing = new Timing("ACCOUNT_CREATE"); + timing.section("Users"); users = new UsersModule(this); timing.section("Storage"); storageModule.run(first); timing.section("Groups"); groups = new GroupsModule(this); - timing.section("App State"); - appStateModule.run(); + timing.section("Conductor"); + conductor.run(); timing.section("Stickers"); stickers = new StickersModule(this); timing.section("Calls"); @@ -167,8 +166,6 @@ public void onLoggedIn(boolean first) { // encryptionModule = new EncryptionModule(this); timing.section("DisplayLists"); displayLists = new DisplayLists(this); - timing.section("DeviceInfo"); - deviceInfoModule = new DeviceInfoModule(this); timing.section("EventBus"); eventBusModule = new EventBusModule(this); @@ -180,16 +177,12 @@ public void onLoggedIn(boolean first) { groups.run(); timing.section("Settings"); settings.run(); - timing.section("DeviceInfo"); - deviceInfoModule.run(); timing.section("Files"); filesModule.run(); timing.section("Search"); search.run(); timing.section("Notifications"); notifications.run(); - timing.section("AppState"); - appStateModule.run(); // timing.section("Encryption"); // encryptionModule.run(); timing.section("Contacts"); @@ -337,11 +330,6 @@ public ProfileModule getProfileModule() { return profile; } - @Override - public AppStateModule getAppStateModule() { - return appStateModule; - } - @Override public PushesModule getPushesModule() { return pushes; @@ -377,11 +365,6 @@ public MentionsModule getMentions() { return mentions; } - @Override - public DeviceInfoModule getDeviceInfoModule() { - return deviceInfoModule; - } - @Override public EncryptionModule getEncryption() { return encryptionModule; @@ -392,6 +375,11 @@ public EventBusModule getEventBus() { return eventBusModule; } + @Override + public ConductorModule getConductor() { + return conductor; + } + @Override public EventBus getEvents() { return events; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/conductor/Conductor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/conductor/Conductor.java new file mode 100644 index 0000000000..c2425625b6 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/conductor/Conductor.java @@ -0,0 +1,37 @@ +package im.actor.core.modules.conductor; + +import im.actor.core.modules.ModuleContext; +import im.actor.runtime.actors.ActorInterface; + +import static im.actor.runtime.actors.ActorSystem.system; + +public class Conductor extends ActorInterface { + + public Conductor(ModuleContext context) { + super(system().actorOf("conductor", () -> new ConductorActor(context))); + } + + public void onContactsLoaded() { + send(new ConductorActor.ContactsLoaded()); + } + + public void onDialogsLoaded() { + send(new ConductorActor.DialogsLoaded()); + } + + public void onSettingsLoaded() { + send(new ConductorActor.SettingsLoaded()); + } + + public void onDialogsChanged(boolean isEmpty) { + send(new ConductorActor.DialogsChanged(isEmpty)); + } + + public void onContactsChanged(boolean isEmpty) { + send(new ConductorActor.ContactsChanged(isEmpty)); + } + + public void onPhoneBookImported() { + send(new ConductorActor.BookImported()); + } +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/conductor/ConductorActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/conductor/ConductorActor.java new file mode 100644 index 0000000000..0adb99cde4 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/conductor/ConductorActor.java @@ -0,0 +1,260 @@ +package im.actor.core.modules.conductor; + +import java.util.ArrayList; + +import im.actor.core.AutoJoinType; +import im.actor.core.api.rpc.RequestNotifyAboutDeviceInfo; +import im.actor.core.modules.ModuleActor; +import im.actor.core.modules.ModuleContext; +import im.actor.core.util.JavaUtil; +import im.actor.core.viewmodel.AppStateVM; +import im.actor.runtime.Log; + +/** + * Launching App in stages + * 1. Download existing Contacts and Dialogs + * 2. Start Phone Book uploading + * 3. When app is fully loaded (at least one contact or message in dialogs) then invoke + * initialization actions such as joining channel + */ +public class ConductorActor extends ModuleActor { + + public static final String TAG = "Conductor"; + + private AppStateVM appStateVM; + + public ConductorActor(ModuleContext context) { + super(context); + } + + @Override + public void preStart() { + super.preStart(); + + updateDeviceInfoIfNeeded(); + + appStateVM = context().getConductor().getAppStateVM(); + + if (appStateVM.isDialogsLoaded() && appStateVM.isContactsLoaded() && appStateVM.isSettingsLoaded()) { + onInitialDataDownloaded(); + } + + if (appStateVM.getIsAppLoaded().get() || !appStateVM.getIsAppEmpty().get()) { + onAppReady(); + } + } + + // + // Initial Loading + // + + public void onDialogsLoaded() { + Log.d(TAG, "Dialogs Loaded"); + if (!appStateVM.isDialogsLoaded()) { + appStateVM.onDialogsLoaded(); + if (appStateVM.isContactsLoaded() && appStateVM.isSettingsLoaded()) { + onInitialDataDownloaded(); + } + } + } + + public void onContactsLoaded() { + Log.d(TAG, "Contacts Loaded"); + if (!appStateVM.isContactsLoaded()) { + appStateVM.onContactsLoaded(); + if (appStateVM.isDialogsLoaded() && appStateVM.isSettingsLoaded()) { + onInitialDataDownloaded(); + } + } + } + + public void onSettingsLoaded() { + Log.d(TAG, "Settings Loaded"); + if (!appStateVM.isSettingsLoaded()) { + appStateVM.onSettingsLoaded(); + if (appStateVM.isDialogsLoaded() && appStateVM.isContactsLoaded()) { + onInitialDataDownloaded(); + } + } + } + + /** + * Called after dialogs, contacts and settings are downloaded from server + */ + public void onInitialDataDownloaded() { + Log.d(TAG, "Initial Data Loaded"); + context().getContactsModule().startImport(); + if (appStateVM.isBookImported()) { + onAppLoaded(); + } + } + + public void onBookImported() { + Log.d(TAG, "Book Uploaded"); + if (!appStateVM.isBookImported()) { + appStateVM.onPhoneImported(); + onAppLoaded(); + } + } + + // + // Data Sync + // + + public void onDialogsChanged(boolean isEmpty) { + boolean wasEmpty = appStateVM.getIsAppEmpty().get(); + appStateVM.onDialogsChanged(isEmpty); + if (wasEmpty && !appStateVM.getIsAppEmpty().get()) { + onAppReady(); + } + } + + public void onContactsChanged(boolean isEmpty) { + boolean wasEmpty = appStateVM.getIsAppEmpty().get(); + appStateVM.onContactsChanged(isEmpty); + if (wasEmpty && !appStateVM.getIsAppEmpty().get()) { + onAppReady(); + } + } + + /** + * Called after dialogs, contacts and settings are downloaded from server and + * phone book is imported to server + */ + public void onAppLoaded() { + Log.d(TAG, "App Loaded"); + + // Joining Groups + if (config().getAutoJoinType() == AutoJoinType.IMMEDIATELY) { + joinGroups(); + } + } + + /** + * Called after at least one message or contact is in user's phone book + */ + public void onAppReady() { + Log.d(TAG, "App Ready"); + + // Joining Groups + if (config().getAutoJoinType() == AutoJoinType.AFTER_INIT) { + joinGroups(); + } + } + + private void joinGroups() { + for (String s : config().getAutoJoinGroups()) { + if (!context().getSettingsModule().getBooleanValue("auto_join." + s, false)) { + context().getGroupsModule().joinGroupByToken(s).then(r -> { + context().getSettingsModule().setBooleanValue("auto_join." + s, true); + }); + } + } + } + + // + // Tools + // + + public void updateDeviceInfoIfNeeded() { + + // + // Loading Information + // + ArrayList langs = new ArrayList<>(); + for (String s : context().getConfiguration().getPreferredLanguages()) { + langs.add(s); + } + final String timeZone = context().getConfiguration().getTimeZone(); + + // + // Checking if information changed + // + String expectedLangs = ""; + for (String s : langs) { + if (!"".equals(expectedLangs)) { + expectedLangs += ","; + } + expectedLangs += s.toLowerCase(); + } + + if (expectedLangs.equals(preferences().getString("device_info_langs")) && + JavaUtil.equalsE(timeZone, preferences().getString("device_info_timezone"))) { + // Already sent + return; + } + + // + // Performing Notification + // + final String finalExpectedLangs = expectedLangs; + api(new RequestNotifyAboutDeviceInfo(langs, timeZone)).then(r -> { + preferences().putString("device_info_langs", finalExpectedLangs); + preferences().putString("device_info_timezone", timeZone); + }); + } + + + // + // Messages + // + + @Override + public void onReceive(Object message) { + if (message instanceof DialogsLoaded) { + onDialogsLoaded(); + } else if (message instanceof ContactsLoaded) { + onContactsLoaded(); + } else if (message instanceof SettingsLoaded) { + onSettingsLoaded(); + } else if (message instanceof BookImported) { + onBookImported(); + } else if (message instanceof ContactsChanged) { + onContactsChanged(((ContactsChanged) message).isEmpty()); + } else if (message instanceof DialogsChanged) { + onDialogsChanged(((DialogsChanged) message).isEmpty()); + } else { + super.onReceive(message); + } + } + + public static class DialogsLoaded { + + } + + public static class ContactsLoaded { + + } + + public static class BookImported { + + } + + public static class SettingsLoaded { + + } + + public static class ContactsChanged { + private boolean isEmpty; + + public ContactsChanged(boolean isEmpty) { + this.isEmpty = isEmpty; + } + + public boolean isEmpty() { + return isEmpty; + } + } + + public static class DialogsChanged { + private boolean isEmpty; + + public DialogsChanged(boolean isEmpty) { + this.isEmpty = isEmpty; + } + + public boolean isEmpty() { + return isEmpty; + } + } +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/conductor/ConductorModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/conductor/ConductorModule.java new file mode 100644 index 0000000000..f81aba4a1f --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/conductor/ConductorModule.java @@ -0,0 +1,36 @@ +package im.actor.core.modules.conductor; + +import im.actor.core.modules.AbsModule; +import im.actor.core.modules.ModuleContext; +import im.actor.core.viewmodel.AppStateVM; +import im.actor.core.viewmodel.GlobalStateVM; + +public class ConductorModule extends AbsModule { + + private AppStateVM appStateVM; + private GlobalStateVM globalStateVM; + private Conductor conductor; + + public ConductorModule(ModuleContext context) { + super(context); + + globalStateVM = new GlobalStateVM(context); + } + + public void run() { + this.appStateVM = new AppStateVM(context()); + this.conductor = new Conductor(context()); + } + + public Conductor getConductor() { + return conductor; + } + + public AppStateVM getAppStateVM() { + return appStateVM; + } + + public GlobalStateVM getGlobalStateVM() { + return globalStateVM; + } +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/misc/DisplayLists.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/conductor/DisplayLists.java similarity index 98% rename from actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/misc/DisplayLists.java rename to actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/conductor/DisplayLists.java index 7bbed42328..e485d92008 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/misc/DisplayLists.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/conductor/DisplayLists.java @@ -2,7 +2,7 @@ * Copyright (C) 2015 Actor LLC. */ -package im.actor.core.modules.misc; +package im.actor.core.modules.conductor; import java.util.HashMap; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/contacts/BookImportActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/contacts/BookImportActor.java index c5418335a1..4d63b49991 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/contacts/BookImportActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/contacts/BookImportActor.java @@ -41,6 +41,9 @@ public class BookImportActor extends ModuleActor { private static final int MAX_IMPORT_SIZE = 50; + // Is Started + private boolean isStarted = false; + // Reading Phone Book private boolean phoneBookReadingIsInProgress = false; @@ -71,15 +74,20 @@ public void preStart() { e.getLocalizedMessage(); } } + } - self().send(new PerformSync()); + private void start() { + if (!isStarted) { + isStarted = true; + self().send(new PerformSync()); + } } private void performSync() { // Ignoring syncing if not enabled if (!config().isEnablePhoneBookImport()) { // Marking as everything is imported - context().getAppStateModule().onBookImported(); + context().getConductor().getConductor().onPhoneBookImported(); return; } @@ -169,7 +177,7 @@ private void performImportIfRequired() { Log.d(TAG, "performImportIfRequired:exiting:nothing to import"); } // Marking as everything is imported - context().getAppStateModule().onBookImported(); + context().getConductor().getConductor().onPhoneBookImported(); return; } @@ -281,14 +289,23 @@ public void onError(RpcException e) { @Override public void onReceive(Object message) { if (message instanceof PerformSync) { + if (!isStarted) { + return; + } performSync(); } else if (message instanceof PhoneBookLoaded) { onPhoneBookLoaded(((PhoneBookLoaded) message).getPhoneBook()); + } else if (message instanceof Start) { + start(); } else { super.onReceive(message); } } + public static class Start { + + } + public static class PerformSync { } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/contacts/ContactsModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/contacts/ContactsModule.java index 733294ba30..723f519cf6 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/contacts/ContactsModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/contacts/ContactsModule.java @@ -58,6 +58,10 @@ public SyncKeyValue getBookImportState() { return bookImportState; } + public void startImport() { + bookImportActor.send(new BookImportActor.Start()); + } + public ListEngine getContacts() { return contacts; } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/contacts/ContactsSyncActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/contacts/ContactsSyncActor.java index 65b5a5eafd..d8aef6bcf2 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/contacts/ContactsSyncActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/contacts/ContactsSyncActor.java @@ -147,7 +147,7 @@ public void onContactsLoaded(ResponseGetContacts result) { isInProgress = false; - context().getAppStateModule().onContactsLoaded(); + context().getConductor().getConductor().onContactsLoaded(); if (result.isNotChanged()) { Log.d(TAG, "Sync: Not changed"); @@ -306,7 +306,7 @@ private void saveList() { } private void notifyState() { - context().getAppStateModule().onContactsUpdate(context().getContactsModule().getContacts().isEmpty()); + context().getConductor().getConductor().onContactsChanged(context().getContactsModule().getContacts().isEmpty()); } @Override diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/dialogs/DialogsActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/dialogs/DialogsActor.java index 384bc97dc5..58e8001e82 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/dialogs/DialogsActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/dialogs/DialogsActor.java @@ -333,7 +333,7 @@ private void notifyState(boolean force) { if (!isEmpty.equals(emptyNotified)) { emptyNotified = isEmpty; - context().getAppStateModule().onDialogsUpdate(isEmpty); + context().getConductor().getConductor().onDialogsChanged(isEmpty); } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/history/DialogsHistoryActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/history/DialogsHistoryActor.java index 555a81c3fe..d0abadd652 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/history/DialogsHistoryActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/history/DialogsHistoryActor.java @@ -45,7 +45,7 @@ public void preStart() { if (!preferences().getBool(KEY_LOADED_INIT, false)) { self().send(new LoadMore()); } else { - context().getAppStateModule().onDialogsLoaded(); + context().getConductor().getConductor().onDialogsLoaded(); } } @@ -99,7 +99,7 @@ private void markAsLoaded() { historyLoaded = true; preferences().putBool(KEY_LOADED, true); preferences().putBool(KEY_LOADED_INIT, true); - context().getAppStateModule().onDialogsLoaded(); + context().getConductor().getConductor().onDialogsLoaded(); } private void markAsSliceLoaded(long date) { @@ -109,7 +109,7 @@ private void markAsSliceLoaded(long date) { preferences().putBool(KEY_LOADED, false); preferences().putBool(KEY_LOADED_INIT, true); preferences().putLong(KEY_LOADED_DATE, date); - context().getAppStateModule().onDialogsLoaded(); + context().getConductor().getConductor().onDialogsLoaded(); } // diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java index 4b5923c64a..4deb34fec6 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java @@ -130,9 +130,11 @@ public void preStart() { showInvite = r.showInvite(); } onActiveDialogsChanged(r.getDialogs(), showArchived, showInvite); + context().getConductor().getConductor().onDialogsLoaded(); }); } else { notifyActiveDialogsVM(); + context().getConductor().getConductor().onDialogsLoaded(); } } @@ -779,7 +781,7 @@ private void notifyActiveDialogsVM() { groups.add(new DialogGroup(i.getTitle(), i.getKey(), dialogSmalls)); } context().getMessagesModule().getDialogGroupsVM().getGroupsValueModel().change(groups); - context().getAppStateModule().getGlobalStateVM().onGlobalCounterChanged(counter); + context().getConductor().getGlobalStateVM().onGlobalCounterChanged(counter); } public boolean isValidPeer(Peer peer) { diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/misc/AppStateModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/misc/AppStateModule.java deleted file mode 100644 index fdc14ed2ee..0000000000 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/misc/AppStateModule.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2015 Actor LLC. - */ - -package im.actor.core.modules.misc; - -import im.actor.core.modules.AbsModule; -import im.actor.core.modules.ModuleContext; -import im.actor.core.viewmodel.AppStateVM; -import im.actor.core.viewmodel.GlobalStateVM; - -public class AppStateModule extends AbsModule { - - private AppStateVM appStateVM; - private GlobalStateVM globalStateVM; - - public AppStateModule(ModuleContext context) { - super(context); - - globalStateVM = new GlobalStateVM(context); - } - - public void run() { - this.appStateVM = new AppStateVM(context()); - } - - public void onDialogsUpdate(boolean isEmpty) { - appStateVM.onDialogsChanged(isEmpty); - } - - public void onContactsUpdate(boolean isEmpty) { - appStateVM.onContactsChanged(isEmpty); - } - - public void onBookImported() { - appStateVM.onPhoneImported(); - } - - public void onContactsLoaded() { - appStateVM.onContactsLoaded(); - } - - public void onDialogsLoaded() { - appStateVM.onDialogsLoaded(); - } - - public AppStateVM getAppStateVM() { - return appStateVM; - } - - public GlobalStateVM getGlobalStateVM() { - return globalStateVM; - } - - public void resetModule() { - // TODO: Implement - } -} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/misc/DeviceInfoActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/misc/DeviceInfoActor.java deleted file mode 100644 index c40aa8076b..0000000000 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/misc/DeviceInfoActor.java +++ /dev/null @@ -1,68 +0,0 @@ -package im.actor.core.modules.misc; - -import java.util.ArrayList; - -import im.actor.core.api.rpc.RequestNotifyAboutDeviceInfo; -import im.actor.core.api.rpc.ResponseVoid; -import im.actor.core.modules.ModuleContext; -import im.actor.core.modules.ModuleActor; -import im.actor.core.network.RpcCallback; -import im.actor.core.network.RpcException; -import im.actor.core.util.JavaUtil; - -public class DeviceInfoActor extends ModuleActor { - - public DeviceInfoActor(ModuleContext context) { - super(context); - } - - @Override - public void preStart() { - super.preStart(); - - // - // Loading Information - // - ArrayList langs = new ArrayList<>(); - for (String s : context().getConfiguration().getPreferredLanguages()) { - langs.add(s); - } - final String timeZone = context().getConfiguration().getTimeZone(); - - // - // Checking if information changed - // - String expectedLangs = ""; - for (String s : langs) { - if (!"".equals(expectedLangs)) { - expectedLangs += ","; - } - expectedLangs += s.toLowerCase(); - } - - if (expectedLangs.equals(preferences().getString("device_info_langs")) && - JavaUtil.equalsE(timeZone, preferences().getString("device_info_timezone"))) { - // Already sent - return; - } - - // - // Performing Notification - // - final String finalExpectedLangs = expectedLangs; - request(new RequestNotifyAboutDeviceInfo(langs, timeZone), new RpcCallback() { - @Override - public void onResult(ResponseVoid response) { - - // Mark as sent - preferences().putString("device_info_langs", finalExpectedLangs); - preferences().putString("device_info_timezone", timeZone); - } - - @Override - public void onError(RpcException e) { - // Ignoring error - } - }); - } -} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/misc/DeviceInfoModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/misc/DeviceInfoModule.java deleted file mode 100644 index 25ff9e6f9c..0000000000 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/misc/DeviceInfoModule.java +++ /dev/null @@ -1,23 +0,0 @@ -package im.actor.core.modules.misc; - -import im.actor.core.modules.AbsModule; -import im.actor.core.modules.ModuleContext; -import im.actor.core.modules.misc.DeviceInfoActor; -import im.actor.runtime.actors.ActorCreator; -import im.actor.runtime.actors.ActorRef; -import im.actor.runtime.actors.Props; - -import static im.actor.runtime.actors.ActorSystem.system; - -public class DeviceInfoModule extends AbsModule { - - private ActorRef actorRef; - - public DeviceInfoModule(ModuleContext context) { - super(context); - } - - public void run() { - actorRef = system().actorOf("device_info/notifier", () -> new DeviceInfoActor(context())); - } -} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/sequence/SequenceActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/sequence/SequenceActor.java index 6fc595db2f..46226268e5 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/sequence/SequenceActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/sequence/SequenceActor.java @@ -305,11 +305,11 @@ private void checkRunnables() { // private void onUpdateStarted() { - context().getAppStateModule().getGlobalStateVM().getIsSyncing().change(true); + context().getConductor().getGlobalStateVM().getIsSyncing().change(true); } private void onUpdateEnded() { - context().getAppStateModule().getGlobalStateVM().getIsSyncing().change(false); + context().getConductor().getGlobalStateVM().getIsSyncing().change(false); } // diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/settings/SettingsModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/settings/SettingsModule.java index 5ba278b548..6dfd60ec0d 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/settings/SettingsModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/settings/SettingsModule.java @@ -392,6 +392,7 @@ private void changeValue(String key, String val) { } settingsSync.send(new SettingsSyncActor.ChangeSettings(key, val)); onUpdatedSetting(key, val); + notifySettingsChanged(); } private String readValue(String key) { @@ -400,6 +401,9 @@ private String readValue(String key) { public void onUpdatedSetting(String key, String value) { preferences().putString(STORAGE_PREFIX + key, value); + } + + public void notifySettingsChanged() { eventBus.post(new SettingsChanged()); } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/settings/SettingsProcessor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/settings/SettingsProcessor.java index 3ed4418748..be0abcca5a 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/settings/SettingsProcessor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/settings/SettingsProcessor.java @@ -20,6 +20,7 @@ public SettingsProcessor(ModuleContext modules) { public void onSettingsChanged(String key, String value) { context().getSettingsModule().onUpdatedSetting(key, value); + context().getSettingsModule().notifySettingsChanged(); } @Override diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/settings/SettingsSyncActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/settings/SettingsSyncActor.java index 280e739113..8cf212d248 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/settings/SettingsSyncActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/settings/SettingsSyncActor.java @@ -56,7 +56,9 @@ public void onResult(ResponseGetParameters response) { for (ApiParameter p : response.getParameters()) { context().getSettingsModule().onUpdatedSetting(p.getKey(), p.getValue()); } + context().getSettingsModule().notifySettingsChanged(); preferences().putBool(SYNC_STATE_LOADED, true); + context().getConductor().getConductor().onSettingsLoaded(); } @Override @@ -64,6 +66,8 @@ public void onError(RpcException e) { // Ignore } }); + } else { + context().getConductor().getConductor().onSettingsLoaded(); } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/AppStateVM.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/AppStateVM.java index 2442b066d7..e5a4d84ba4 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/AppStateVM.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/AppStateVM.java @@ -32,6 +32,7 @@ public class AppStateVM { private boolean isBookImported; private boolean isDialogsLoaded; private boolean isContactsLoaded; + private boolean isSettingsLoaded; /** * Constructor of View Model @@ -47,12 +48,13 @@ public AppStateVM(ModuleContext context) { this.isBookImported = context.getPreferences().getBool("app.contacts.imported", false); this.isDialogsLoaded = context.getPreferences().getBool("app.dialogs.loaded", false); this.isContactsLoaded = context.getPreferences().getBool("app.contacts.loaded", false); + this.isSettingsLoaded = context.getPreferences().getBool("app.settings.loaded", false); this.isAppLoaded = new BooleanValueModel("app.loaded", isBookImported && isDialogsLoaded && isContactsLoaded); } private void updateLoaded() { - boolean val = isBookImported && isDialogsLoaded && isContactsLoaded; + boolean val = isBookImported && isDialogsLoaded && isContactsLoaded && isSettingsLoaded; if (isAppLoaded.get() != val) { this.isAppLoaded.change(val); } @@ -128,6 +130,17 @@ public synchronized void onContactsLoaded() { } } + /** + * Notify from Modules about contacts load completed + */ + public synchronized void onSettingsLoaded() { + if (!isSettingsLoaded) { + isSettingsLoaded = true; + context.getPreferences().putBool("app.settings.loaded", true); + updateLoaded(); + } + } + /** * Dialogs empty View Model * @@ -164,4 +177,39 @@ public BooleanValueModel getIsAppEmpty() { return isAppEmpty; } + /** + * Is Phone Book imported + * + * @return is phone book imported state + */ + public boolean isBookImported() { + return isBookImported; + } + + /** + * Is dialogs loaded + * + * @return is dialogs loaded state + */ + public boolean isDialogsLoaded() { + return isDialogsLoaded; + } + + /** + * Is contacts loaded + * + * @return is contacts loaded state + */ + public boolean isContactsLoaded() { + return isContactsLoaded; + } + + /** + * Is settings loaded + * + * @return is contacts loaded state + */ + public boolean isSettingsLoaded() { + return isSettingsLoaded; + } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GlobalStateVM.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GlobalStateVM.java index 55a2e5bc77..91738f99ae 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GlobalStateVM.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GlobalStateVM.java @@ -30,25 +30,19 @@ public GlobalStateVM(ModuleContext context) { this.isConnecting = new BooleanValueModel("app.connecting", false); this.isSyncing = new BooleanValueModel("app.syncing", false); - context.getEvents().subscribe(new BusSubscriber() { - @Override - public void onBusEvent(Event event) { - if (event instanceof AppVisibleChanged) { - if (((AppVisibleChanged) event).isVisible()) { - isAppVisible.change(true); - globalTempCounter.change(0); - } else { - isAppVisible.change(false); - } + context.getEvents().subscribe(event -> { + if (event instanceof AppVisibleChanged) { + if (((AppVisibleChanged) event).isVisible()) { + isAppVisible.change(true); + globalTempCounter.change(0); + } else { + isAppVisible.change(false); } } }, AppVisibleChanged.EVENT); - context.getEvents().subscribe(new BusSubscriber() { - @Override - public void onBusEvent(Event event) { - isConnecting.change(((ConnectingStateChanged) event).isConnecting()); - } + context.getEvents().subscribe(event -> { + isConnecting.change(((ConnectingStateChanged) event).isConnecting()); }, ConnectingStateChanged.EVENT); } From eaa6b25a7a651a25f6385f047064a6e6949b2d68 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Tue, 2 Aug 2016 00:56:35 +0300 Subject: [PATCH 222/414] fix(js): Fixing compilation errors --- .../main/java/im/actor/core/js/entity/JsGroup.java | 11 ++++++----- .../im/actor/core/js/modules/JsBindingModule.java | 10 +++++----- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/actor-sdk/sdk-core/core/core-js/src/main/java/im/actor/core/js/entity/JsGroup.java b/actor-sdk/sdk-core/core/core-js/src/main/java/im/actor/core/js/entity/JsGroup.java index b61dff0bc5..9bccd9b11a 100644 --- a/actor-sdk/sdk-core/core/core-js/src/main/java/im/actor/core/js/entity/JsGroup.java +++ b/actor-sdk/sdk-core/core/core-js/src/main/java/im/actor/core/js/entity/JsGroup.java @@ -6,6 +6,7 @@ import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.core.client.JsArray; + import im.actor.core.entity.Avatar; import im.actor.core.entity.GroupMember; import im.actor.core.entity.Peer; @@ -21,7 +22,7 @@ public class JsGroup extends JavaScriptObject { public static JsGroup fromGroupVM(GroupVM groupVM, JsMessenger messenger) { int online = groupVM.getPresence().get(); - String presence = messenger.getFormatter().formatGroupMembers(groupVM.getMembersCount()); + String presence = messenger.getFormatter().formatGroupMembers(groupVM.getMembersCount().get()); if (online > 0) { presence += ", " + messenger.getFormatter().formatGroupOnline(online); } @@ -45,7 +46,7 @@ public static JsGroup fromGroupVM(GroupVM groupVM, JsMessenger messenger) { // Log.d("JsGroup", "PeerInfo: " + peerInfo); convertedMembers.add(JsGroupMember.create(peerInfo, g.isAdministrator(), - g.getInviterUid() == messenger.myUid() || groupVM.getCreatorId() == messenger.myUid())); + g.getInviterUid() == messenger.myUid())); } Collections.sort(convertedMembers, new Comparator() { @Override @@ -58,16 +59,16 @@ public int compare(JsGroupMember o1, JsGroupMember o2) { jsMembers.push(member); } return create(groupVM.getId(), groupVM.getName().get(), groupVM.getAbout().get(), fileUrl, bigFileUrl, - Placeholders.getPlaceholder(groupVM.getId()), groupVM.getCreatorId(), presence, + Placeholders.getPlaceholder(groupVM.getId()), presence, jsMembers); } public static native JsGroup create(int id, String name, String about, String avatar, String bigAvatar, - String placeholder, int adminId, String presence, + String placeholder, String presence, JsArray members)/*-{ return { id: id, name: name, about: about, avatar: avatar, bigAvatar: bigAvatar, placeholder: placeholder, - adminId: adminId, presence: presence, members: members + presence: presence, members: members }; }-*/; diff --git a/actor-sdk/sdk-core/core/core-js/src/main/java/im/actor/core/js/modules/JsBindingModule.java b/actor-sdk/sdk-core/core/core-js/src/main/java/im/actor/core/js/modules/JsBindingModule.java index bf33db3ca4..f257742787 100644 --- a/actor-sdk/sdk-core/core/core-js/src/main/java/im/actor/core/js/modules/JsBindingModule.java +++ b/actor-sdk/sdk-core/core/core-js/src/main/java/im/actor/core/js/modules/JsBindingModule.java @@ -165,7 +165,7 @@ public void onChanged(ArrayList val, Value> public JsBindedValue getOnlineStatus() { if (onlineState == null) { - final GlobalStateVM vm = context().getAppStateModule().getGlobalStateVM(); + final GlobalStateVM vm = context().getConductor().getGlobalStateVM(); onlineState = new JsBindedValue<>("online"); vm.getIsConnecting().subscribe(new ValueChangedListener() { @@ -307,11 +307,11 @@ public void onChanged(Integer val, Value valueModel) { value.changeValue(null); return; } - String presence = messenger.getFormatter().formatGroupMembers(groupVM.getMembersCount()); + String presence = messenger.getFormatter().formatGroupMembers(groupVM.getMembersCount().get()); if (val > 0) { presence += ", " + messenger.getFormatter().formatGroupOnline(val); } - value.changeValue(JsOnlineGroup.create(groupVM.getMembersCount(), val, presence, false)); + value.changeValue(JsOnlineGroup.create(groupVM.getMembersCount().get(), val, presence, false)); } else { value.changeValue(JsOnlineGroup.create(0, 0, "Not member", false)); } @@ -430,7 +430,7 @@ public JsDisplayList getSharedMessageList(Peer peer) { public JsBindedValue getGlobalCounter() { if (globalCounter == null) { - ValueModel counter = context().getAppStateModule().getGlobalStateVM().getGlobalCounter(); + ValueModel counter = context().getConductor().getGlobalStateVM().getGlobalCounter(); globalCounter = new JsBindedValue<>(JsCounter.create(counter.get())); counter.subscribe(new ValueChangedListener() { @Override @@ -444,7 +444,7 @@ public void onChanged(Integer val, Value valueModel) { public JsBindedValue getTempGlobalCounter() { if (tempGlobalCounter == null) { - ValueModel counter = context().getAppStateModule().getGlobalStateVM().getGlobalTempCounter(); + ValueModel counter = context().getConductor().getGlobalStateVM().getGlobalTempCounter(); tempGlobalCounter = new JsBindedValue<>(JsCounter.create(counter.get())); counter.subscribe(new ValueChangedListener() { @Override From e951b8c0b0d95e9c35662d6d0e0607d1856fa303 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Tue, 2 Aug 2016 01:14:01 +0300 Subject: [PATCH 223/414] feat+fix(iOS,core): Auto join to groups in iOS, fixing crash in ConductorActor --- .../sdk-core-ios/ActorApp/AppDelegate.swift | 2 + .../ActorSDK/Sources/ActorSDK.swift | 15 +++++++ .../java/im/actor/core/modules/Modules.java | 9 ++-- .../core/modules/conductor/Conductor.java | 4 ++ .../modules/conductor/ConductorActor.java | 42 +++++++++++++++++++ .../modules/conductor/ConductorModule.java | 4 ++ 6 files changed, 70 insertions(+), 6 deletions(-) diff --git a/actor-sdk/sdk-core-ios/ActorApp/AppDelegate.swift b/actor-sdk/sdk-core-ios/ActorApp/AppDelegate.swift index 3773b66dca..70eee0d887 100644 --- a/actor-sdk/sdk-core-ios/ActorApp/AppDelegate.swift +++ b/actor-sdk/sdk-core-ios/ActorApp/AppDelegate.swift @@ -30,6 +30,8 @@ import ActorSDK ActorSDK.sharedActor().style.dialogAvatarSize = 58 + ActorSDK.sharedActor().autoJoinGroups = ["actor_news"] + // Creating Actor ActorSDK.sharedActor().createActor() diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorSDK.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorSDK.swift index 8c2f8ebcb7..0116857588 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorSDK.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorSDK.swift @@ -138,6 +138,11 @@ import DZNWebViewController /// Enable experimental features public var enableExperimentalFeatures: Bool = false + /// Auto Join Groups + public var autoJoinGroups = [String]() + + /// Should perform auto join only after first message or contact + public var autoJoinOnReady = true // // User Onlines @@ -256,6 +261,16 @@ import DZNWebViewController log("Found time zone :\(timeZone)") builder.setTimeZone(timeZone) + // AutoJoin + for s in autoJoinGroups { + builder.addAutoJoinGroupWithToken(s) + } + if autoJoinOnReady { + builder.setAutoJoinType(ACAutoJoinType.AFTER_INIT()) + } else { + builder.setAutoJoinType(ACAutoJoinType.IMMEDIATELY()) + } + // Logs // builder.setEnableFilesLogging(true) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/Modules.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/Modules.java index ea51a103f6..cca0a8b41a 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/Modules.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/Modules.java @@ -197,17 +197,14 @@ public void onLoggedIn(boolean first) { calls.run(); timing.section("Stickers"); stickers.run(); + timing.section("Conductor:end"); + conductor.runAfter(); timing.end(); if (Runtime.isMainThread()) { messenger.onLoggedIn(); } else { - Runtime.postToMainThread(new Runnable() { - @Override - public void run() { - messenger.onLoggedIn(); - } - }); + Runtime.postToMainThread(() -> messenger.onLoggedIn()); } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/conductor/Conductor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/conductor/Conductor.java index c2425625b6..1cf4037965 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/conductor/Conductor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/conductor/Conductor.java @@ -11,6 +11,10 @@ public Conductor(ModuleContext context) { super(system().actorOf("conductor", () -> new ConductorActor(context))); } + public void finishLaunching() { + send(new ConductorActor.FinishLaunching()); + } + public void onContactsLoaded() { send(new ConductorActor.ContactsLoaded()); } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/conductor/ConductorActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/conductor/ConductorActor.java index 0adb99cde4..9b616db53b 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/conductor/ConductorActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/conductor/ConductorActor.java @@ -4,6 +4,7 @@ import im.actor.core.AutoJoinType; import im.actor.core.api.rpc.RequestNotifyAboutDeviceInfo; +import im.actor.core.api.rpc.ResponseVoid; import im.actor.core.modules.ModuleActor; import im.actor.core.modules.ModuleContext; import im.actor.core.util.JavaUtil; @@ -20,8 +21,11 @@ public class ConductorActor extends ModuleActor { public static final String TAG = "Conductor"; + private static final ResponseVoid DUMB = null; + private static final Integer DUMB2 = null; private AppStateVM appStateVM; + private boolean isStarted = false; public ConductorActor(ModuleContext context) { super(context); @@ -34,7 +38,15 @@ public void preStart() { updateDeviceInfoIfNeeded(); appStateVM = context().getConductor().getAppStateVM(); + } + public void onFinishLaunching() { + if (isStarted) { + return; + } + isStarted = true; + unstashAll(); + if (appStateVM.isDialogsLoaded() && appStateVM.isContactsLoaded() && appStateVM.isSettingsLoaded()) { onInitialDataDownloaded(); } @@ -202,17 +214,43 @@ public void updateDeviceInfoIfNeeded() { @Override public void onReceive(Object message) { if (message instanceof DialogsLoaded) { + if (!isStarted) { + stash(); + return; + } onDialogsLoaded(); } else if (message instanceof ContactsLoaded) { + if (!isStarted) { + stash(); + return; + } onContactsLoaded(); } else if (message instanceof SettingsLoaded) { + if (!isStarted) { + stash(); + return; + } onSettingsLoaded(); } else if (message instanceof BookImported) { + if (!isStarted) { + stash(); + return; + } onBookImported(); } else if (message instanceof ContactsChanged) { + if (!isStarted) { + stash(); + return; + } onContactsChanged(((ContactsChanged) message).isEmpty()); } else if (message instanceof DialogsChanged) { + if (!isStarted) { + stash(); + return; + } onDialogsChanged(((DialogsChanged) message).isEmpty()); + } else if (message instanceof FinishLaunching) { + onFinishLaunching(); } else { super.onReceive(message); } @@ -257,4 +295,8 @@ public boolean isEmpty() { return isEmpty; } } + + public static class FinishLaunching { + + } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/conductor/ConductorModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/conductor/ConductorModule.java index f81aba4a1f..fb58a1c169 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/conductor/ConductorModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/conductor/ConductorModule.java @@ -22,6 +22,10 @@ public void run() { this.conductor = new Conductor(context()); } + public void runAfter() { + this.conductor.finishLaunching(); + } + public Conductor getConductor() { return conductor; } From f9b1499d98878332863061a5ce7c589e902c1cd4 Mon Sep 17 00:00:00 2001 From: Alashow Date: Tue, 2 Aug 2016 04:32:39 +0500 Subject: [PATCH 224/414] Update ui_text.xml Dirty "au" string removed. --- .../sdk-core-android/android-sdk/src/main/res/values/ui_text.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml index 7290f30972..be5628f390 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml @@ -142,7 +142,6 @@ Please, enter your phone number Please, enter your email The code has expired. Please perform validation again. - au The code is invalid. Please try again. Authorization server error. Please, try again. From d69c6235cf8843ac9315cf56141c4e89b7a68888 Mon Sep 17 00:00:00 2001 From: rockjam Date: Mon, 1 Aug 2016 20:03:37 +0300 Subject: [PATCH 225/414] refactor(server): mark deprecated methods with @silent --- .../server/group/AdminCommandHandlers.scala | 11 +++++---- .../server/group/GroupCommandHandlers.scala | 7 +++--- .../im/actor/server/group/GroupErrors.scala | 2 ++ .../im/actor/server/group/GroupMigrator.scala | 7 +++--- .../server/group/GroupQueryHandlers.scala | 14 +++++++---- .../im/actor/server/group/GroupState.scala | 23 +++++++++++-------- .../server/group/InfoCommandHandlers.scala | 7 +++--- .../server/group/MemberCommandHandlers.scala | 15 ++++++------ .../server/group/http/GroupsHttpHandler.scala | 3 ++- .../migrations/HiddenGroupMigrator.scala | 3 ++- .../server/names/GlobalNamesStorage.scala | 11 +++++---- .../im/actor/server/office/Processor.scala | 2 +- .../server/frontend/MTProtoBlueprint.scala | 7 +++--- .../server/frontend/PackageCheckStage.scala | 3 ++- .../im/actor/server/frontend/WsFrontend.scala | 2 ++ .../scala/im/actor/server/persist/Group.scala | 2 +- .../scala/im/actor/api/rpc/PeerHelpers.scala | 9 -------- .../service/groups/GroupsServiceImpl.scala | 13 ++++++----- .../service/messaging/HistoryHandlers.scala | 13 ++++------- actor-server/project/Build.scala | 18 ++++++--------- actor-server/project/Dependencies.scala | 4 +++- 21 files changed, 93 insertions(+), 83 deletions(-) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/AdminCommandHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/AdminCommandHandlers.scala index df37b3b8e4..2e145fcc79 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/AdminCommandHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/AdminCommandHandlers.scala @@ -5,6 +5,7 @@ import java.time.Instant import akka.actor.Status import akka.pattern.pipe import akka.http.scaladsl.util.FastFuture +import com.github.ghik.silencer.silent import im.actor.api.rpc.Update import im.actor.api.rpc.groups._ import im.actor.api.rpc.messaging.UpdateChatClear @@ -35,7 +36,7 @@ private[group] trait AdminCommandHandlers extends GroupsImplicits { val newState = commit(evt) //TODO: remove deprecated - db.run(GroupBotRepo.updateToken(groupId, newToken)) + db.run(GroupBotRepo.updateToken(groupId, newToken): @silent) val result: Future[RevokeIntegrationTokenAck] = for { _ ← oldToken match { @@ -75,7 +76,7 @@ private[group] trait AdminCommandHandlers extends GroupsImplicits { val updateObsolete = UpdateGroupMembersUpdateObsolete(groupId, members) //TODO: remove deprecated - db.run(GroupUserRepo.makeAdmin(groupId, cmd.candidateUserId)) + db.run(GroupUserRepo.makeAdmin(groupId, cmd.candidateUserId): @silent) val adminGROUPUpdates: Future[SeqStateDate] = for { @@ -160,7 +161,7 @@ private[group] trait AdminCommandHandlers extends GroupsImplicits { val updateObsolete = UpdateGroupMembersUpdateObsolete(groupId, members) //TODO: remove deprecated - db.run(GroupUserRepo.dismissAdmin(groupId, cmd.targetUserId)) + db.run(GroupUserRepo.dismissAdmin(groupId, cmd.targetUserId): @silent) val adminGROUPUpdates: Future[SeqState] = for { @@ -340,8 +341,8 @@ private[group] trait AdminCommandHandlers extends GroupsImplicits { exMemberIds foreach { userId ⇒ db.run( for { - _ ← GroupUserRepo.delete(groupId, userId) - _ ← GroupInviteTokenRepo.revoke(groupId, userId) + _ ← GroupUserRepo.delete(groupId, userId): @silent + _ ← GroupInviteTokenRepo.revoke(groupId, userId): @silent } yield () ) } diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala index 0eb5380506..dc3d8588fa 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupCommandHandlers.scala @@ -5,6 +5,7 @@ import java.time.Instant import akka.actor.Status import akka.http.scaladsl.util.FastFuture import akka.pattern.pipe +import com.github.ghik.silencer.silent import im.actor.api.rpc.Update import im.actor.api.rpc.groups._ import im.actor.api.rpc.users.ApiSex @@ -103,8 +104,8 @@ private[group] trait GroupCommandHandlers ), cmd.randomId, isHidden = false - ) - _ ← GroupUserRepo.create(groupId, cmd.creatorUserId, cmd.creatorUserId, createdAt, None, isAdmin = true) + ): @silent + _ ← GroupUserRepo.create(groupId, cmd.creatorUserId, cmd.creatorUserId, createdAt, None, isAdmin = true): @silent } yield () ) @@ -151,7 +152,7 @@ private[group] trait GroupCommandHandlers val newState = commit(evt) //TODO: remove deprecated - db.run(GroupBotRepo.create(groupId, botUserId, botToken)) + db.run(GroupBotRepo.create(groupId, botUserId, botToken): @silent) (for { _ ← userExt.create(botUserId, ACLUtils.nextAccessSalt(), None, "Bot", "US", ApiSex.Unknown, isBot = true) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupErrors.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupErrors.scala index d2e06a8c18..5080d09a9b 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupErrors.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupErrors.scala @@ -42,4 +42,6 @@ object GroupErrors { case object NoPermission extends Exception with NoStackTrace case object CantLeaveGroup extends Exception with NoStackTrace + + final case class IncorrectGroupType(value: Int) extends Exception with NoStackTrace } diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupMigrator.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupMigrator.scala index ab83851a7a..4ccea6d4f4 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupMigrator.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupMigrator.scala @@ -9,6 +9,7 @@ import scala.concurrent.{ ExecutionContext, Future, Promise } import akka.actor.{ ActorSystem, Props } import akka.pattern.pipe import akka.persistence.RecoveryCompleted +import com.github.ghik.silencer.silent import im.actor.server.db.DbExtension import org.joda.time.DateTime import im.actor.server.event.TSEvent @@ -49,12 +50,12 @@ private final class GroupMigrator(promise: Promise[Unit], groupId: Int) extends override def persistenceId = GroupProcessor.persistenceIdFor(groupId) private def migrate(): Unit = { - db.run(GroupRepo.findFull(groupId)) foreach { + db.run(GroupRepo.findFull(groupId): @silent) foreach { case Some(group) ⇒ db.run(for { avatarOpt ← AvatarDataRepo.findByGroupId(groupId) - bots ← GroupBotRepo.findByGroup(groupId) map (_.map(Seq(_)).getOrElse(Seq.empty)) - users ← GroupUserRepo.find(groupId) + bots ← (GroupBotRepo.findByGroup(groupId): @silent) map (_.map(Seq(_)).getOrElse(Seq.empty)) + users ← GroupUserRepo.find(groupId): @silent } yield Migrate( group = group, avatarData = avatarOpt, diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala index 65bf91f2b4..3b3e769ccd 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala @@ -6,7 +6,7 @@ import akka.stream.scaladsl.Source import com.google.protobuf.ByteString import com.google.protobuf.wrappers.Int32Value import im.actor.api.rpc.groups._ -import im.actor.server.group.GroupErrors.{ NoPermission, NotOwner } +import im.actor.server.group.GroupErrors.{ IncorrectGroupType, NoPermission, NotOwner } import im.actor.server.group.GroupQueries._ import im.actor.server.group.GroupType.{ Channel, General, Unrecognized } @@ -77,6 +77,7 @@ trait GroupQueryHandlers { case Channel ⇒ if (state.isAdmin(clientUserId)) load else FastFuture.successful(LoadMembersResponse(Seq.empty, offsetBs)) + case Unrecognized(v) ⇒ throw IncorrectGroupType(v) } } @@ -110,8 +111,9 @@ trait GroupQueryHandlers { ext = None, membersCount = Some(count), groupType = Some(state.groupType match { - case Channel ⇒ ApiGroupType.CHANNEL - case General | Unrecognized(_) ⇒ ApiGroupType.GROUP + case Channel ⇒ ApiGroupType.CHANNEL + case General ⇒ ApiGroupType.GROUP + case Unrecognized(v) ⇒ throw IncorrectGroupType(v) }), permissions = Some(state.permissions.groupFor(clientUserId)), isDeleted = Some(state.isDeleted) @@ -147,8 +149,9 @@ trait GroupQueryHandlers { FastFuture.successful { val canSend = state.bot.exists(_.userId == clientUserId) || { state.groupType match { - case General ⇒ state.isMember(clientUserId) - case Channel ⇒ state.isAdmin(clientUserId) + case General ⇒ state.isMember(clientUserId) + case Channel ⇒ state.isAdmin(clientUserId) + case Unrecognized(v) ⇒ throw IncorrectGroupType(v) } } CanSendMessageResponse( @@ -201,6 +204,7 @@ trait GroupQueryHandlers { apiMembers → group.membersCount else apiMembers.find(_.userId == clientUserId).toVector → group.membersCount + case Unrecognized(v) ⇒ throw IncorrectGroupType(v) } } else { Vector.empty[ApiMember] → 0 diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala index 01bc29da71..e124305eb1 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala @@ -7,8 +7,9 @@ import im.actor.api.rpc.groups.ApiAdminSettings import im.actor.api.rpc.misc.ApiExtension import im.actor.server.cqrs.{ Event, ProcessorState } import im.actor.server.file.Avatar +import im.actor.server.group.GroupErrors.IncorrectGroupType import im.actor.server.group.GroupEvents._ -import im.actor.server.group.GroupType.{ Channel, General } +import im.actor.server.group.GroupType.{ Channel, General, Unrecognized } private[group] final case class Member( userId: Int, @@ -159,8 +160,9 @@ private[group] final case class GroupState( def getShowableOwner(clientUserId: Int): Option[Int] = groupType match { - case General ⇒ Some(creatorUserId) - case Channel ⇒ if (isAdmin(clientUserId)) Some(creatorUserId) else None + case General ⇒ Some(creatorUserId) + case Channel ⇒ if (isAdmin(clientUserId)) Some(creatorUserId) else None + case Unrecognized(v) ⇒ throw IncorrectGroupType(v) } override def updated(e: Event): GroupState = e match { @@ -332,8 +334,9 @@ private[group] final case class GroupState( private def canSendMessage(clientUserId: Int) = { groupType match { - case General ⇒ isMember(clientUserId) - case Channel ⇒ isAdmin(clientUserId) || isOwner(clientUserId) + case General ⇒ isMember(clientUserId) + case Channel ⇒ isAdmin(clientUserId) || isOwner(clientUserId) + case Unrecognized(v) ⇒ throw IncorrectGroupType(v) } } || bot.exists(_.userId == clientUserId) @@ -412,8 +415,9 @@ private[group] final case class GroupState( */ def canViewMembers(clientUserId: Int) = groupType match { - case General ⇒ isMember(clientUserId) - case Channel ⇒ isAdmin(clientUserId) || isOwner(clientUserId) + case General ⇒ isMember(clientUserId) + case Channel ⇒ isAdmin(clientUserId) || isOwner(clientUserId) + case Unrecognized(v) ⇒ throw IncorrectGroupType(v) } /** @@ -455,8 +459,9 @@ private[group] final case class GroupState( */ def canKickInvited(userId: Int): Boolean = groupType match { - case General ⇒ isMember(userId) - case Channel ⇒ isAdmin(userId) || isOwner(userId) + case General ⇒ isMember(userId) + case Channel ⇒ isAdmin(userId) || isOwner(userId) + case Unrecognized(v) ⇒ throw IncorrectGroupType(v) } /** diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/InfoCommandHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/InfoCommandHandlers.scala index 389cfc7f0a..462075a826 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/InfoCommandHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/InfoCommandHandlers.scala @@ -5,6 +5,7 @@ import java.time.Instant import akka.actor.Status import akka.pattern.pipe import akka.http.scaladsl.util.FastFuture +import com.github.ghik.silencer.silent import im.actor.api.rpc.files.ApiAvatar import im.actor.api.rpc.groups._ import im.actor.server.file.{ Avatar, ImageUtils } @@ -97,7 +98,7 @@ private[group] trait InfoCommandHandlers { val pushRules = seqUpdExt.pushRules(isFat = false, Some(PushTexts.titleChanged(newState.groupType))) //TODO: remove deprecated - db.run(GroupRepo.updateTitle(groupId, title, cmd.clientUserId, cmd.randomId, date = evt.ts)) + db.run(GroupRepo.updateTitle(groupId, title, cmd.clientUserId, cmd.randomId, date = evt.ts): @silent) val result: Future[SeqStateDate] = for { @@ -171,7 +172,7 @@ private[group] trait InfoCommandHandlers { val pushRules = seqUpdExt.pushRules(isFat = false, Some(PushTexts.topicChanged(newState.groupType))) //TODO: remove deprecated - db.run(GroupRepo.updateTopic(groupId, topic)) + db.run(GroupRepo.updateTopic(groupId, topic): @silent) val result: Future[SeqStateDate] = for { @@ -235,7 +236,7 @@ private[group] trait InfoCommandHandlers { val pushRules = seqUpdExt.pushRules(isFat = false, Some(PushTexts.topicChanged(newState.groupType))) //TODO: remove deprecated - db.run(GroupRepo.updateAbout(groupId, about)) + db.run(GroupRepo.updateAbout(groupId, about): @silent) val result: Future[SeqStateDate] = for { diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/MemberCommandHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/MemberCommandHandlers.scala index 16a4b64d61..9d105b351a 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/MemberCommandHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/MemberCommandHandlers.scala @@ -5,6 +5,7 @@ import java.time.{ Instant, LocalDateTime, ZoneOffset } import akka.actor.Status import akka.http.scaladsl.util.FastFuture import akka.pattern.pipe +import com.github.ghik.silencer.silent import im.actor.api.rpc.Update import im.actor.api.rpc.groups._ import im.actor.api.rpc.messaging.{ ApiServiceMessage, UpdateChatDropCache, UpdateMessage } @@ -83,7 +84,7 @@ private[group] trait MemberCommandHandlers extends GroupsImplicits { val serviceMessage = GroupServiceMessages.userInvited(cmd.inviteeUserId) //TODO: remove deprecated - db.run(GroupUserRepo.create(groupId, cmd.inviteeUserId, cmd.inviterUserId, evt.ts, None, isAdmin = false)) + db.run(GroupUserRepo.create(groupId, cmd.inviteeUserId, cmd.inviterUserId, evt.ts, None, isAdmin = false): @silent) def inviteGROUPUpdates: Future[SeqStateDate] = for { @@ -215,7 +216,7 @@ private[group] trait MemberCommandHandlers extends GroupsImplicits { val wasInvited = state.isInvited(cmd.joiningUserId) // trying to figure out who invited joining user. - // Descdending priority: + // Descending priority: // • inviter defined in `Join` command (when invited via token) // • inviter from members list (when invited by other user) // • group creator (safe fallback) @@ -292,7 +293,7 @@ private[group] trait MemberCommandHandlers extends GroupsImplicits { invitedAt = optMember.map(_.invitedAt).getOrElse(date), joinedAt = Some(LocalDateTime.now(ZoneOffset.UTC)), isAdmin = false - )) + ): @silent) def joinGROUPUpdates: Future[SeqStateDate] = for { @@ -447,8 +448,8 @@ private[group] trait MemberCommandHandlers extends GroupsImplicits { //TODO: remove deprecated. GroupInviteTokenRepo don't have replacement yet. db.run( for { - _ ← GroupUserRepo.delete(groupId, cmd.userId) - _ ← GroupInviteTokenRepo.revoke(groupId, cmd.userId) + _ ← GroupUserRepo.delete(groupId, cmd.userId): @silent + _ ← GroupInviteTokenRepo.revoke(groupId, cmd.userId): @silent } yield () ) @@ -582,8 +583,8 @@ private[group] trait MemberCommandHandlers extends GroupsImplicits { //TODO: remove deprecated. GroupInviteTokenRepo don't have replacement yet. db.run( for { - _ ← GroupUserRepo.delete(groupId, cmd.kickedUserId) - _ ← GroupInviteTokenRepo.revoke(groupId, cmd.kickedUserId) + _ ← GroupUserRepo.delete(groupId, cmd.kickedUserId): @silent + _ ← GroupInviteTokenRepo.revoke(groupId, cmd.kickedUserId): @silent } yield () ) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/http/GroupsHttpHandler.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/http/GroupsHttpHandler.scala index e8d3b80506..a34d0b4443 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/http/GroupsHttpHandler.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/http/GroupsHttpHandler.scala @@ -6,6 +6,7 @@ import akka.http.scaladsl.model.StatusCodes._ import akka.http.scaladsl.server.Directives._ import akka.http.scaladsl.server.Route import akka.http.scaladsl.util.FastFuture +import com.github.ghik.silencer.silent import im.actor.server.api.http.json.JsonFormatters.{ errorsFormat, groupInviteInfoFormat } import im.actor.server.api.http.{ HttpHandler, json } import im.actor.server.db.DbExtension @@ -50,7 +51,7 @@ private[group] final class GroupsHttpHandler()(implicit system: ActorSystem) ext } private def retrieve(tokenOrShortName: String): Future[Either[json.Errors, json.GroupInviteInfo]] = for { - byToken ← db.run(GroupInviteTokenRepo.findByToken(tokenOrShortName)) + byToken ← db.run(GroupInviteTokenRepo.findByToken(tokenOrShortName): @silent) byGroupId ← globalNamesStorage.getGroupId(tokenOrShortName) optInviteData = (byToken, byGroupId) match { case (Some(tokenInfo), _) ⇒ Some(tokenInfo.groupId → Some(tokenInfo.creatorId)) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/migrations/HiddenGroupMigrator.scala b/actor-server/actor-core/src/main/scala/im/actor/server/migrations/HiddenGroupMigrator.scala index 1ee868947e..37bb78b9aa 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/migrations/HiddenGroupMigrator.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/migrations/HiddenGroupMigrator.scala @@ -2,6 +2,7 @@ package im.actor.server.migrations import akka.actor.{ ActorLogging, ActorSystem, PoisonPill, Props } import akka.persistence.{ PersistentActor, RecoveryCompleted } +import com.github.ghik.silencer.silent import im.actor.concurrent.FutureExt import im.actor.server.db.DbExtension import im.actor.server.event.TSEvent @@ -52,7 +53,7 @@ private final class HiddenGroupMigrator(promise: Promise[Unit], groupId: Int) ex private def migrate(): Unit = { if (isHidden) { - db.run(GroupRepo.makeHidden(groupId)) onComplete { + db.run(GroupRepo.makeHidden(groupId): @silent) onComplete { case Failure(e) ⇒ promise.failure(e) self ! PoisonPill diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/names/GlobalNamesStorage.scala b/actor-server/actor-core/src/main/scala/im/actor/server/names/GlobalNamesStorage.scala index a78b47e909..207af1e126 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/names/GlobalNamesStorage.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/names/GlobalNamesStorage.scala @@ -2,6 +2,7 @@ package im.actor.server.names import akka.actor.ActorSystem import akka.http.scaladsl.util.FastFuture +import com.github.ghik.silencer.silent import im.actor.server.db.DbExtension import im.actor.server.persist.UserRepo import im.actor.storage.SimpleStorage @@ -58,7 +59,7 @@ final class GlobalNamesStorageKeyValueStorage(implicit system: ActorSystem) { Some(GlobalNameOwner.parseFrom(bytes)) filter (_.ownerType.isUser) map (o ⇒ o.ownerId → fullName) } } - val compatSearch = db.run(UserRepo.findByNicknamePrefix(namePrefix)) map { users ⇒ + val compatSearch = db.run(UserRepo.findByNicknamePrefix(namePrefix): @silent) map { users ⇒ users flatMap { user ⇒ user.nickname map (user.id → _) } @@ -82,7 +83,7 @@ final class GlobalNamesStorageKeyValueStorage(implicit system: ActorSystem) { existsInKV flatMap { case true ⇒ FastFuture.successful(true) - case false ⇒ db.run(UserRepo.nicknameExists(name)) + case false ⇒ db.run(UserRepo.nicknameExists(name): @silent) } } @@ -111,7 +112,7 @@ final class GlobalNamesStorageKeyValueStorage(implicit system: ActorSystem) { optOwner flatMap { case o @ Some(_) ⇒ FastFuture.successful(o) - case None ⇒ db.run(UserRepo.findByNickname(name)) map (_.map(u ⇒ GlobalNameOwner(OwnerType.User, u.id))) + case None ⇒ db.run(UserRepo.findByNickname(name): @silent) map (_.map(u ⇒ GlobalNameOwner(OwnerType.User, u.id))) } } @@ -130,9 +131,9 @@ final class GlobalNamesStorageKeyValueStorage(implicit system: ActorSystem) { if (count == 0) { db.run { for { - optUser ← UserRepo.findByNickname(name) + optUser ← UserRepo.findByNickname(name): @silent _ ← optUser match { - case Some(u) ⇒ UserRepo.setNickname(u.id, None) + case Some(u) ⇒ UserRepo.setNickname(u.id, None): @silent case None ⇒ DBIO.successful(0) } } yield () diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/office/Processor.scala b/actor-server/actor-core/src/main/scala/im/actor/server/office/Processor.scala index cfcbfd7625..3988b2e8b8 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/office/Processor.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/office/Processor.scala @@ -22,7 +22,7 @@ case object StopOffice trait ProcessorState -@deprecated("use im.actor.server.cqrs.Processor instead", "2016-07-07") +// TODO: replace with im.actor.server.cqrs.Processor trait Processor[State, Event <: AnyRef] extends PersistentActor with ActorFutures with AlertingActor { case class BreakStashing(ts: Instant, evts: Seq[Event], state: State) diff --git a/actor-server/actor-frontend/src/main/scala/im/actor/server/frontend/MTProtoBlueprint.scala b/actor-server/actor-frontend/src/main/scala/im/actor/server/frontend/MTProtoBlueprint.scala index 19f20eb8fc..a34a5ab998 100644 --- a/actor-server/actor-frontend/src/main/scala/im/actor/server/frontend/MTProtoBlueprint.scala +++ b/actor-server/actor-frontend/src/main/scala/im/actor/server/frontend/MTProtoBlueprint.scala @@ -3,6 +3,7 @@ package im.actor.server.frontend import java.net.InetAddress import akka.stream.FlowShape +import com.github.ghik.silencer.silent import kamon.metric.instrument.{ MinMaxCounter, Histogram } import scala.util.{ Failure, Success } @@ -32,11 +33,11 @@ object MTProtoBlueprint { val sessionClientSource = Source.fromPublisher(ActorPublisher[MTProto](sessionClient)) - val mtprotoFlow = Flow.fromGraph(new PackageParseStage()) + @silent val mtprotoFlow = Flow.fromGraph(new PackageParseStage()) .transform(() ⇒ new PackageCheckStage) .via(new PackageHandleStage(protoVersions, apiMajorVersions, authManager, sessionClient)) - val mapRespFlow: Flow[MTProto, ByteString, akka.NotUsed] = Flow[MTProto] + @silent val mapRespFlow: Flow[MTProto, ByteString, akka.NotUsed] = Flow[MTProto] .transform(() ⇒ mapResponse(system)) val connStartTime = System.currentTimeMillis() @@ -82,7 +83,7 @@ object MTProtoBlueprint { }) } - def mapResponse(system: ActorSystem) = new PushStage[MTProto, ByteString] { + @silent def mapResponse(system: ActorSystem) = new PushStage[MTProto, ByteString] { private[this] var packageIndex: Int = -1 override def onPush(elem: MTProto, ctx: Context[ByteString]) = { diff --git a/actor-server/actor-frontend/src/main/scala/im/actor/server/frontend/PackageCheckStage.scala b/actor-server/actor-frontend/src/main/scala/im/actor/server/frontend/PackageCheckStage.scala index 489f13041c..882965594b 100644 --- a/actor-server/actor-frontend/src/main/scala/im/actor/server/frontend/PackageCheckStage.scala +++ b/actor-server/actor-frontend/src/main/scala/im/actor/server/frontend/PackageCheckStage.scala @@ -1,9 +1,10 @@ package im.actor.server.frontend import akka.stream.stage.{ Context, PushStage, SyncDirective } - +import com.github.ghik.silencer.silent import im.actor.server.mtproto.transport.{ Handshake, TransportPackage } +@silent private[frontend] final class PackageCheckStage extends PushStage[TransportPackage, TransportPackage] { private trait State diff --git a/actor-server/actor-frontend/src/main/scala/im/actor/server/frontend/WsFrontend.scala b/actor-server/actor-frontend/src/main/scala/im/actor/server/frontend/WsFrontend.scala index 0f62209df4..8849da19a1 100644 --- a/actor-server/actor-frontend/src/main/scala/im/actor/server/frontend/WsFrontend.scala +++ b/actor-server/actor-frontend/src/main/scala/im/actor/server/frontend/WsFrontend.scala @@ -10,6 +10,7 @@ import akka.stream.{ ActorMaterializer, Materializer } import akka.stream.scaladsl._ import akka.stream.stage.{ Context, PushStage, SyncDirective, TerminationDirective } import akka.util.ByteString +import com.github.ghik.silencer.silent import im.actor.server.session.SessionRegion import scala.concurrent.duration._ @@ -66,6 +67,7 @@ object WsFrontend extends Frontend("ws") { .via(completionFlow(System.currentTimeMillis())) } + @silent def completionFlow[T](connStartTime: Long)(implicit system: ActorSystem): Flow[T, T, akka.NotUsed] = Flow[T] .transform(() ⇒ new PushStage[T, T] { diff --git a/actor-server/actor-persist/src/main/scala/im/actor/server/persist/Group.scala b/actor-server/actor-persist/src/main/scala/im/actor/server/persist/Group.scala index 86bb7cc3e0..79e4decb5f 100644 --- a/actor-server/actor-persist/src/main/scala/im/actor/server/persist/Group.scala +++ b/actor-server/actor-persist/src/main/scala/im/actor/server/persist/Group.scala @@ -93,7 +93,7 @@ object GroupRepo { ) } - @deprecated("Replace with some sort of key-value maybe?", "2016-06-05") + // TODO: Replace with key value def findAllIds = allIds.result @deprecated("Remove, only used in tests", "2016-06-05") diff --git a/actor-server/actor-rpc-api/src/main/scala/im/actor/api/rpc/PeerHelpers.scala b/actor-server/actor-rpc-api/src/main/scala/im/actor/api/rpc/PeerHelpers.scala index af4797e4d9..52e6b8d0e1 100644 --- a/actor-server/actor-rpc-api/src/main/scala/im/actor/api/rpc/PeerHelpers.scala +++ b/actor-server/actor-rpc-api/src/main/scala/im/actor/api/rpc/PeerHelpers.scala @@ -62,15 +62,6 @@ object PeerHelpers { accessHashCheck(checkGroupOutPeers(groupOutPeers), authorizedAction) } - //TODO: remove in future - @deprecated("Use Future inner type instead", "2016-07-07") - def withOutPeerDBIO[R <: RpcResponse](outPeer: ApiOutPeer)(f: ⇒ DBIO[RpcError Xor R])( - implicit - client: AuthorizedClientData, - system: ActorSystem - ): DBIO[RpcError Xor R] = - DBIO.from(withOutPeer(outPeer)(DbExtension(system).db.run(f))) - private def accessHashCheck[R <: RpcResponse](check: Future[Boolean], authorizedAction: ⇒ Future[RpcError Xor R])(implicit ec: ExecutionContext) = check flatMap { isValid ⇒ if (isValid) { diff --git a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupsServiceImpl.scala b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupsServiceImpl.scala index c6876f852a..e2de35d632 100644 --- a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupsServiceImpl.scala +++ b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupsServiceImpl.scala @@ -5,6 +5,7 @@ import java.time.Instant import akka.actor.ActorSystem import akka.http.scaladsl.util.FastFuture import cats.data.Xor +import com.github.ghik.silencer.silent import im.actor.api.rpc.PeerHelpers._ import im.actor.api.rpc._ import im.actor.api.rpc.files.ApiFileLocation @@ -356,12 +357,12 @@ final class GroupsServiceImpl(groupInviteConfig: GroupInviteConfig)(implicit act } else { withGroupOutPeer(groupPeer) { db.run(for { - token ← GroupInviteTokenRepo.find(groupPeer.groupId, client.userId).headOption.flatMap { + token ← (GroupInviteTokenRepo.find(groupPeer.groupId, client.userId): @silent).headOption.flatMap { case Some(invToken) ⇒ DBIO.successful(invToken.token) case None ⇒ val token = ACLUtils.accessToken(ThreadLocalSecureRandom.current()) val inviteToken = GroupInviteToken(groupPeer.groupId, client.userId, token) - for (_ ← GroupInviteTokenRepo.create(inviteToken)) yield token + for (_ ← GroupInviteTokenRepo.create(inviteToken): @silent) yield token } } yield Ok(ResponseInviteUrl(genInviteUrl(token)))) } @@ -383,7 +384,7 @@ final class GroupsServiceImpl(groupInviteConfig: GroupInviteConfig)(implicit act joinInfo ← joinSting match { case Xor.Left(token) ⇒ for { - info ← fromFutureOption(GroupRpcErrors.InvalidInviteToken)(db.run(GroupInviteTokenRepo.findByToken(token))) + info ← fromFutureOption(GroupRpcErrors.InvalidInviteToken)(db.run(GroupInviteTokenRepo.findByToken(token): @silent)) } yield info.groupId → Some(info.creatorId) case Xor.Right(groupName) ⇒ for { @@ -441,10 +442,10 @@ final class GroupsServiceImpl(groupInviteConfig: GroupInviteConfig)(implicit act val token = ACLUtils.accessToken() db.run( for { - _ ← GroupInviteTokenRepo.revoke(groupPeer.groupId, client.userId) + _ ← GroupInviteTokenRepo.revoke(groupPeer.groupId, client.userId): @silent _ ← GroupInviteTokenRepo.create( GroupInviteToken(groupPeer.groupId, client.userId, token) - ) + ): @silent } yield Ok(ResponseInviteUrl(genInviteUrl(token))) ) } @@ -591,7 +592,7 @@ final class GroupsServiceImpl(groupInviteConfig: GroupInviteConfig)(implicit act result ← if (isHistoryShared) { db.run( for { - member ← GroupUserRepo.find(groupPeer.groupId, client.userId) + member ← GroupUserRepo.find(groupPeer.groupId, client.userId): @silent response ← member match { case Some(_) ⇒ DBIO.successful(Error(GroupRpcErrors.AlreadyInvited)) case None ⇒ diff --git a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/messaging/HistoryHandlers.scala b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/messaging/HistoryHandlers.scala index fb55eed14d..7ad08bd90a 100644 --- a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/messaging/HistoryHandlers.scala +++ b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/messaging/HistoryHandlers.scala @@ -10,16 +10,13 @@ import im.actor.api.rpc.misc.{ ResponseSeq, ResponseVoid } import im.actor.api.rpc.peers.{ ApiGroupOutPeer, ApiOutPeer, ApiPeerType, ApiUserOutPeer } import im.actor.api.rpc.sequence.ApiUpdateOptimization import im.actor.server.dialog.HistoryUtils -import im.actor.server.group.GroupQueries.CanSendMessageResponse import im.actor.server.group.{ CanSendMessageInfo, GroupUtils } -import im.actor.server.model.{ DialogObsolete, HistoryMessage, Peer, PeerType } +import im.actor.server.model.Peer import im.actor.server.persist.contact.UserContactRepo -import im.actor.server.persist.dialog.DialogRepo -import im.actor.server.persist.{ GroupUserRepo, HistoryMessageRepo } +import im.actor.server.persist.HistoryMessageRepo import im.actor.server.sequence.SeqState import im.actor.server.user.UserUtils import org.joda.time.DateTime -import slick.dbio import slick.driver.PostgresDriver.api._ import scala.concurrent.Future @@ -196,9 +193,9 @@ trait HistoryHandlers { clientData: ClientData ): Future[HandlerResult[ResponseLoadHistory]] = authorized(clientData) { implicit client ⇒ - val action = withOutPeerDBIO(peer) { + withOutPeer(peer) { val modelPeer = peer.asModel - for { + val action = for { historyOwner ← DBIO.from(getHistoryOwner(modelPeer, client.userId)) (lastReceivedAt, lastReadAt) ← getLastReceiveReadDates(modelPeer) messageModels ← mode match { @@ -240,8 +237,8 @@ trait HistoryHandlers { groupPeers = groups map (g ⇒ ApiGroupOutPeer(g.id, g.accessHash)) )) } + db.run(action) } - db.run(action) } override def doHandleDeleteMessage(outPeer: ApiOutPeer, randomIds: IndexedSeq[Long], clientData: ClientData): Future[HandlerResult[ResponseSeq]] = diff --git a/actor-server/project/Build.scala b/actor-server/project/Build.scala index f35f67fcb9..ab7842b941 100644 --- a/actor-server/project/Build.scala +++ b/actor-server/project/Build.scala @@ -82,7 +82,8 @@ object Build extends sbt.Build with Versioning with Releasing with Packaging { }, resolvers ++= Resolvers.seq, fork in Test := false, - updateOptions := updateOptions.value.withCachedResolution(true) + updateOptions := updateOptions.value.withCachedResolution(true), + addCompilerPlugin("com.github.ghik" % "silencer-plugin" % "0.4") ) lazy val root = Project( @@ -174,8 +175,7 @@ object Build extends sbt.Build with Versioning with Releasing with Packaging { id = "actor-core", base = file("actor-core"), settings = defaultSettingsServer ++ SbtActorApi.settings ++ Seq( - libraryDependencies ++= Dependencies.core, - scalacOptions in Compile := (scalacOptions in Compile).value.filterNot(_ == "-Xfatal-warnings") + libraryDependencies ++= Dependencies.core ) ) .dependsOn(actorCodecs, actorFileAdapter, actorModels, actorPersist, actorRuntime) @@ -194,8 +194,7 @@ object Build extends sbt.Build with Versioning with Releasing with Packaging { id = "actor-enrich", base = file("actor-enrich"), settings = defaultSettingsServer ++ Seq( - libraryDependencies ++= Dependencies.enrich, - scalacOptions in Compile := (scalacOptions in Compile).value.filterNot(_ == "-Xfatal-warnings") + libraryDependencies ++= Dependencies.enrich ) ) .dependsOn(actorRpcApi, actorRuntime) @@ -247,8 +246,7 @@ object Build extends sbt.Build with Versioning with Releasing with Packaging { id = "actor-rpc-api", base = file("actor-rpc-api"), settings = defaultSettingsServer ++ Seq( - libraryDependencies ++= Dependencies.rpcApi, - scalacOptions in Compile := (scalacOptions in Compile).value.filterNot(_ == "-Xfatal-warnings") + libraryDependencies ++= Dependencies.rpcApi ) ) .dependsOn( @@ -269,8 +267,7 @@ object Build extends sbt.Build with Versioning with Releasing with Packaging { id = "actor-fs-adapters", base = file("actor-fs-adapters"), settings = defaultSettingsServer ++ Seq( - libraryDependencies ++= Dependencies.fileAdapter, - scalacOptions in Compile := (scalacOptions in Compile).value.filterNot(_ == "-Xfatal-warnings") + libraryDependencies ++= Dependencies.fileAdapter ) ) .dependsOn(actorHttpApi, actorPersist) @@ -279,8 +276,7 @@ object Build extends sbt.Build with Versioning with Releasing with Packaging { id = "actor-frontend", base = file("actor-frontend"), settings = defaultSettingsServer ++ Seq( - libraryDependencies ++= Dependencies.frontend, - scalacOptions in Compile := (scalacOptions in Compile).value.filterNot(_ == "-Xfatal-warnings") + libraryDependencies ++= Dependencies.frontend ) ) .dependsOn(actorCore, actorSession) diff --git a/actor-server/project/Dependencies.scala b/actor-server/project/Dependencies.scala index e466c43c8c..446beaac53 100644 --- a/actor-server/project/Dependencies.scala +++ b/actor-server/project/Dependencies.scala @@ -109,6 +109,7 @@ object Dependencies { val guava = "com.google.guava" % "guava" % "19.0" val alpn = "org.eclipse.jetty.alpn" % "alpn-api" % "1.1.2.v20150522" % "runtime" val tcnative = "io.netty" % "netty-tcnative" % "1.1.33.Fork15" classifier "linux-x86_64" + val silencer = "com.github.ghik" % "silencer-lib" % "0.4" } object Testing { @@ -134,7 +135,8 @@ object Dependencies { scalaLogging, tyrex, kamonCore, - kamonDatadog + kamonDatadog, + silencer ) val root = shared ++ Seq( From c5b94e543c21e3308d2821335e910cc47a711125 Mon Sep 17 00:00:00 2001 From: rockjam Date: Tue, 2 Aug 2016 04:20:10 +0300 Subject: [PATCH 226/414] fix(server:enrich): update history messages in groups with shared history --- .../server/messaging/MessageUpdating.scala | 29 ++++++++++++++----- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/messaging/MessageUpdating.scala b/actor-server/actor-core/src/main/scala/im/actor/server/messaging/MessageUpdating.scala index d2744e80e7..620affd8d1 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/messaging/MessageUpdating.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/messaging/MessageUpdating.scala @@ -5,6 +5,7 @@ import im.actor.api.rpc.PeersImplicits import im.actor.api.rpc.messaging.{ ApiMessage, UpdateMessageContentChanged } import im.actor.api.rpc.peers.{ ApiPeer, ApiPeerType } import im.actor.server.db.DbExtension +import im.actor.server.dialog.HistoryUtils import im.actor.server.group.GroupExtension import im.actor.server.model.{ Peer, PeerType } import im.actor.server.persist.HistoryMessageRepo @@ -72,6 +73,7 @@ trait MessageUpdating extends PeersImplicits { deliveryId = s"msgcontent_${randomId}_${date}" ) (memberIds, _, optBotId) ← GroupExtension(system).getMemberIds(groupPeer.id) + isShared ← GroupExtension(system).isHistoryShared(groupPeer.id) membersSet = (memberIds ++ optBotId.toSeq).toSet // update for other group members _ ← seqUpdExt.broadcastPeopleUpdate( @@ -80,14 +82,25 @@ trait MessageUpdating extends PeersImplicits { pushRules = seqUpdExt.pushRules(isFat = false, None), deliveryId = s"msgcontent_${randomId}_${date}" ) - _ ← DbExtension(system).db.run(HistoryMessageRepo.updateContentAll( - userIds = membersSet + userId, - randomId = randomId, - peerType = PeerType.Group, - peerIds = Set(groupPeer.id), - messageContentHeader = updatedMessage.header, - messageContentData = updatedMessage.toByteArray - )) + _ ← if (isShared) { + DbExtension(system).db.run(HistoryMessageRepo.updateContentAll( + userIds = Set(HistoryUtils.SharedUserId), + randomId = randomId, + peerType = PeerType.Group, + peerIds = Set(groupPeer.id), + messageContentHeader = updatedMessage.header, + messageContentData = updatedMessage.toByteArray + )) + } else { + DbExtension(system).db.run(HistoryMessageRepo.updateContentAll( + userIds = membersSet + userId, + randomId = randomId, + peerType = PeerType.Group, + peerIds = Set(groupPeer.id), + messageContentHeader = updatedMessage.header, + messageContentData = updatedMessage.toByteArray + )) + } } yield seqState } From 292e68937e806956d87ae31c0aa49e165e3776cb Mon Sep 17 00:00:00 2001 From: kor_ka Date: Wed, 3 Aug 2016 15:18:19 +0300 Subject: [PATCH 227/414] fix(android): start delegated activity after login --- .../main/java/im/actor/sdk/controllers/auth/AuthActivity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/auth/AuthActivity.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/auth/AuthActivity.java index c6cb930c8e..417b13b02c 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/auth/AuthActivity.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/auth/AuthActivity.java @@ -157,7 +157,7 @@ private void updateState(AuthState state, boolean force) { break; case LOGGED_IN: finish(); - startActivity(new Intent(this, RootActivity.class)); + ActorSDK.sharedActor().startMessagingApp(this); break; } } From f6c7317112d387022282299a2058228c76519549 Mon Sep 17 00:00:00 2001 From: rockjam Date: Tue, 2 Aug 2016 22:19:37 +0300 Subject: [PATCH 228/414] fix(server): bump dialog method; some cleanup in history methods --- .../actor-core/src/main/protobuf/dialog.proto | 9 +++ .../server/dialog/DialogCommandHandlers.scala | 71 +++++++++++++------ .../actor/server/dialog/DialogExtension.scala | 45 +++++++----- .../im/actor/server/dialog/DialogRoot.scala | 59 ++++++--------- .../actor/server/dialog/DialogRootState.scala | 2 +- .../im/actor/server/dialog/HistoryUtils.scala | 8 ++- .../server/group/MemberCommandHandlers.scala | 33 ++++++--- .../server/user/UserCommandHandlers.scala | 14 ++-- .../im/actor/server/user/UserProcessor.scala | 3 - actor-server/project/Build.scala | 8 ++- 10 files changed, 153 insertions(+), 99 deletions(-) diff --git a/actor-server/actor-core/src/main/protobuf/dialog.proto b/actor-server/actor-core/src/main/protobuf/dialog.proto index bd5016f130..0346d683ea 100644 --- a/actor-server/actor-core/src/main/protobuf/dialog.proto +++ b/actor-server/actor-core/src/main/protobuf/dialog.proto @@ -135,6 +135,14 @@ message DialogRootCommands { Peer dest = 1; int64 client_auth_id = 2; } + + message Bump { + option (scalapb.message).extends = "im.actor.server.dialog.DialogRootCommand"; + + Peer dest = 1; + } + + message BumpAck {} } message DialogRootQueries { @@ -200,6 +208,7 @@ message DialogRootEnvelope { DialogRootCommands.Favourite favourite = 7; DialogRootCommands.Unfavourite unfavourite = 8; DialogRootCommands.Delete delete = 9; + DialogRootCommands.Bump bump = 12; } } diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogCommandHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogCommandHandlers.scala index e5c093afa0..21ae5faa7f 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogCommandHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogCommandHandlers.scala @@ -5,7 +5,6 @@ import java.time.Instant import akka.actor.Status import akka.http.scaladsl.util.FastFuture import akka.pattern.pipe -import com.google.protobuf.wrappers.Int64Value import im.actor.api.rpc.PeersImplicits import im.actor.api.rpc.messaging._ import im.actor.server.ApiConversions._ @@ -17,7 +16,6 @@ import im.actor.server.pubsub.{ PeerMessage, PubSubExtension } import im.actor.server.sequence.{ SeqState, SeqStateDate } import im.actor.server.social.SocialManager import im.actor.util.cache.CacheHelpers._ -import org.joda.time.DateTime import scala.concurrent.Future import scala.util.Failure @@ -57,19 +55,31 @@ trait DialogCommandHandlers extends PeersImplicits with UserAcl { case true ⇒ FastFuture.failed(NotUniqueRandomId) case false ⇒ withNonBlockedPeer[SeqStateDate](userId, sm.getDest)( - default = for { - SendMessageAck(updatedSender) ← dialogExt.ackSendMessage(peer, sm.withDate(sendDate)) - finalPeer = updatedSender getOrElse selfPeer - _ ← db.run(writeHistoryMessage(finalPeer, peer, new DateTime(sendDate), sm.randomId, message.header, message.toByteArray)) - //_ = dialogExt.updateCounters(peer, userId) - SeqState(seq, state) ← deliveryExt.senderDelivery(userId, optClientAuthId, peer, sm.randomId, sendDate, message, sm.isFat, sm.deliveryTag) - } yield SeqStateDate(seq, state, sendDate), - failed = for { - _ ← db.run(writeHistoryMessageSelf(userId, peer, userId, new DateTime(sendDate), sm.randomId, message.header, message.toByteArray)) - SeqState(seq, state) ← deliveryExt.senderDelivery(userId, optClientAuthId, peer, sm.randomId, sendDate, message, sm.isFat, sm.deliveryTag) - } yield { - SeqStateDate(seq, state, sendDate) - } + default = + for { + SendMessageAck(updatedSender) ← dialogExt.ackSendMessage(peer, sm.withDate(sendDate)) + finalPeer = updatedSender getOrElse selfPeer + _ ← db.run(writeHistoryMessage(finalPeer, peer, sendDate, sm.randomId, message.header, message.toByteArray)) + //_ = dialogExt.updateCounters(peer, userId) + _ ← dialogExt.bump(userId, peer) + SeqState(seq, state) ← deliveryExt.senderDelivery( + senderUserId = userId, + senderAuthId = optClientAuthId, + peer = peer, + randomId = sm.randomId, + timestamp = sendDate, + message = message, + isFat = sm.isFat, + deliveryTag = sm.deliveryTag + ) + } yield SeqStateDate(seq, state, sendDate), + failed = + for { + _ ← db.run(writeHistoryMessageSelf(userId, peer, userId, sendDate, sm.randomId, message.header, message.toByteArray)) + SeqState(seq, state) ← deliveryExt.senderDelivery(userId, optClientAuthId, peer, sm.randomId, sendDate, message, sm.isFat, sm.deliveryTag) + } yield { + SeqStateDate(seq, state, sendDate) + } ) } } yield seqStateDate @@ -90,10 +100,19 @@ trait DialogCommandHandlers extends PeersImplicits with UserAcl { SocialManager.recordRelation(userId, sm.getOrigin.id) } - deliveryExt - .receiverDelivery(userId, sm.getOrigin.id, peer, sm.randomId, messageDate, sm.message, sm.isFat, sm.deliveryTag) - .map(_ ⇒ SendMessageAck()) - .pipeTo(sender()) + (for { + _ ← dialogExt.bump(userId, peer) + _ ← deliveryExt.receiverDelivery( + receiverUserId = userId, + senderUserId = sm.getOrigin.id, + peer = peer, + randomId = sm.randomId, + timestamp = messageDate, + message = sm.message, + isFat = sm.isFat, + deliveryTag = sm.deliveryTag + ) + } yield SendMessageAck()) pipeTo sender() deliveryExt.sendCountersUpdate(userId) } @@ -110,8 +129,18 @@ trait DialogCommandHandlers extends PeersImplicits with UserAcl { } else { persist(NewMessage(randomId, Instant.ofEpochMilli(dateMillis), senderUserId, message.header)) { e ⇒ commit(e) - db.run(writeHistoryMessageSelf(userId, peer, senderUserId, new DateTime(dateMillis), randomId, message.header, message.toByteArray)) - .map(_ ⇒ WriteMessageSelfAck()) pipeTo sender() + (for { + _ ← dialogExt.bump(userId, peer) + _ ← db.run(writeHistoryMessageSelf( + userId, + peer, + senderUserId, + dateMillis, + randomId, + message.header, + message.toByteArray + )) + } yield WriteMessageSelfAck()) pipeTo sender() } } } diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogExtension.scala b/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogExtension.scala index 3b7d1c5220..4d0ad5ea27 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogExtension.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogExtension.scala @@ -15,6 +15,7 @@ import im.actor.api.rpc.peers.ApiPeer import im.actor.extension.InternalExtensions import im.actor.server.db.DbExtension import im.actor.server.dialog.DialogCommands._ +import im.actor.server.dialog.DialogRootCommands.BumpAck import im.actor.server.group.{ GroupEnvelope, GroupExtension } import im.actor.server.model._ import im.actor.server.persist.HistoryMessageRepo @@ -100,7 +101,7 @@ final class DialogExtensionImpl(system: ActorSystem) extends DialogExtension wit senderUserId = senderUserId, sendMessage = SendMessage( origin = Some(Peer.privat(senderUserId)), - dest = Some(peer.asModel), + dest = Some(mPeer), senderAuthId = None, date = None, randomId = randomId, @@ -155,25 +156,26 @@ final class DialogExtensionImpl(system: ActorSystem) extends DialogExtension wit def ackSendMessage(peer: Peer, sm: SendMessage): Future[SendMessageAck] = (processorRegion(peer) ? envelope(peer, DialogEnvelope().withSendMessage(sm))).mapTo[SendMessageAck] - def writeMessage( - peer: ApiPeer, - senderUserId: Int, - date: Instant, - randomId: Long, - message: ApiMessage - ): Future[Unit] = - withValidPeer(peer.asModel, senderUserId, failed = FastFuture.successful(())) { - for { - memberIds ← fetchMemberIds(DialogId(peer.asModel, senderUserId)) - _ ← Future.sequence(memberIds map (writeMessageSelf(_, peer, senderUserId, new DateTime(date.toEpochMilli), randomId, message))) - } yield () - } + // TODO: figure out if we still need it. + // def writeMessage( + // peer: ApiPeer, + // senderUserId: Int, + // date: Instant, + // randomId: Long, + // message: ApiMessage + // ): Future[Unit] = + // withValidPeer(peer.asModel, senderUserId, failed = FastFuture.successful(())) { + // for { + // memberIds ← fetchMemberIds(DialogId(peer.asModel, senderUserId)) + // _ ← Future.sequence(memberIds map (writeMessageSelf(_, peer, senderUserId, new DateTime(date.toEpochMilli), randomId, message))) + // } yield () + // } def writeMessageSelf( userId: Int, peer: ApiPeer, senderUserId: Int, - date: DateTime, + dateMillis: Long, randomId: Long, message: ApiMessage ): Future[Unit] = @@ -182,7 +184,7 @@ final class DialogExtensionImpl(system: ActorSystem) extends DialogExtension wit envelope(Peer.privat(userId), DialogEnvelope().withWriteMessageSelf(WriteMessageSelf( dest = Some(peer.asModel), senderUserId, - date.getMillis, + dateMillis, randomId, message )))) map (_ ⇒ ()) @@ -260,6 +262,17 @@ final class DialogExtensionImpl(system: ActorSystem) extends DialogExtension wit .mapTo[SeqState] } + def bump(userId: Int, peer: Peer): Future[Unit] = + withValidPeer(peer, userId, failed = Future.failed[Unit](DialogErrors.MessageToSelf)) { + (userExt.processorRegion.ref ? + UserEnvelope(userId) + .withDialogRootEnvelope( + DialogRootEnvelope().withBump(DialogRootCommands.Bump(Some(peer))) + )) + .mapTo[BumpAck] + .map(_ ⇒ ()) + } + def setReaction(userId: Int, authId: Long, peer: Peer, randomId: Long, code: String): Future[SetReactionAck] = withValidPeer(peer, userId) { (userExt.processorRegion.ref ? UserEnvelope(userId).withDialogEnvelope(DialogEnvelope().withSetReaction(SetReaction( diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogRoot.scala b/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogRoot.scala index a177f55057..33a2c23a1d 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogRoot.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogRoot.scala @@ -136,36 +136,13 @@ private class DialogRoot(val userId: Int, extensions: Seq[ApiExtension]) } override protected def handleCommand: Receive = { - case dc: DialogCommand if dc.isInstanceOf[SendMessage] || dc.isInstanceOf[WriteMessageSelf] ⇒ - needCheckDialog(dc) match { - case peerOpt @ Some(peer) ⇒ - val now = Instant.now - - val isCreated = isDialogCreated(peer) - val isShown = isDialogShown(peer) - - val events = if (isCreated) { - (if (!isShown) List(Unarchived(now, peerOpt)) else List.empty) ++ - (if (!isDialogOnTop(peer)) List(Bumped(now, peerOpt)) else List.empty) - } else { - log.debug("Creating dialog with peer type: {} id: {}", peerOpt.get.`type`, peerOpt.get.id) - List(Created(now, peerOpt)) - } - - persistAllAsync(events)(e ⇒ commit(e)) - - deferAsync(()) { _ ⇒ - if (!isCreated || !isShown) - sendChatGroupsChanged(0L) - } - case None ⇒ - } - case CheckArchive() ⇒ checkArchive() + case Bump(Some(peer)) ⇒ bump(peer) case Archive(Some(peer), clientAuthId) ⇒ archive(peer, clientAuthId) case Unarchive(Some(peer), clientAuthId) ⇒ unarchive(peer, clientAuthId) case Favourite(Some(peer), clientAuthId) ⇒ favourite(peer, clientAuthId) case Unfavourite(Some(peer), clientAuthId) ⇒ unfavourite(peer, clientAuthId) case Delete(Some(peer), clientAuthId) ⇒ delete(peer, clientAuthId) + case CheckArchive() ⇒ checkArchive() } private def checkArchive(): Unit = @@ -226,18 +203,26 @@ private class DialogRoot(val userId: Int, extensions: Seq[ApiExtension]) } } - private def needCheckDialog(cmd: DialogCommand): Option[Peer] = { - cmd match { - case sm: SendMessage ⇒ - Some(sm.getOrigin.typ match { - case PeerType.Group ⇒ sm.getDest - case PeerType.Private ⇒ - if (selfPeer == sm.getDest) sm.getOrigin - else sm.getDest - case _ ⇒ throw new RuntimeException("Unknown peer type") - }) - case wm: WriteMessageSelf ⇒ Some(wm.getDest) - case _ ⇒ None + private def bump(peer: Peer) = { + val now = Instant.now + + val isCreated = isDialogCreated(peer) + val isShown = isDialogShown(peer) + + val createdEvt = if (!isCreated) Some(Created(now).withPeer(peer)) else None + val shownEvt = if (isCreated && !isShown) Some(Unarchived(now).withPeer(peer)) else None + val bumpedEvt = if (isCreated && !isDialogOnTop(peer)) Some(Bumped(now).withPeer(peer)) else None + + val events: List[DialogRootEvent] = (createdEvt ++ shownEvt ++ bumpedEvt).toList + + persistAllAsync(events)(e ⇒ commit(e)) + + val replyTo = sender() + deferAsync(()) { _ ⇒ + (if (!isCreated || !isShown) + sendChatGroupsChanged(0L) map (_ ⇒ BumpAck()) + else + FastFuture.successful(BumpAck())) pipeTo replyTo } } diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogRootState.scala b/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogRootState.scala index 57a223c8aa..32f292bae1 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogRootState.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogRootState.scala @@ -263,4 +263,4 @@ private[dialog] final case class DialogRootState( copy(active = newActive) } -} \ No newline at end of file +} diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/dialog/HistoryUtils.scala b/actor-server/actor-core/src/main/scala/im/actor/server/dialog/HistoryUtils.scala index c4f800d492..13066aee40 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/dialog/HistoryUtils.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/dialog/HistoryUtils.scala @@ -21,7 +21,7 @@ object HistoryUtils { private[dialog] def writeHistoryMessage( fromPeer: Peer, toPeer: Peer, - date: DateTime, + dateMillis: Long, randomId: Long, messageContentHeader: Int, messageContentData: Array[Byte] @@ -30,6 +30,8 @@ object HistoryUtils { requirePrivatePeer(fromPeer) // requireDifferentPeers(fromPeer, toPeer) + val date = new DateTime(dateMillis) + if (toPeer.typ == PeerType.Private) { val outMessage = HistoryMessage( userId = fromPeer.id, @@ -79,7 +81,7 @@ object HistoryUtils { userId: Int, toPeer: Peer, senderUserId: Int, - date: DateTime, + dateMillis: Long, randomId: Long, messageContentHeader: Int, messageContentData: Array[Byte] @@ -88,7 +90,7 @@ object HistoryUtils { _ ← HistoryMessageRepo.create(HistoryMessage( userId = userId, peer = toPeer, - date = date, + date = new DateTime(dateMillis), senderUserId = senderUserId, randomId = randomId, messageContentHeader = messageContentHeader, diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/MemberCommandHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/MemberCommandHandlers.scala index 9d105b351a..22eef90f4b 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/MemberCommandHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/MemberCommandHandlers.scala @@ -157,6 +157,7 @@ private[group] trait MemberCommandHandlers extends GroupsImplicits { ), deliveryTag = Some(Optimization.GroupV2) ) + _ ← dialogExt.bump(cmd.inviteeUserId, apiGroupPeer.asModel) } yield SeqStateDate(seq, state, dateMillis) val result: Future[SeqStateDate] = for { @@ -330,17 +331,28 @@ private[group] trait MemberCommandHandlers extends GroupsImplicits { serviceMessage // no delivery tag. This updated handled this way in Groups V1 ) map (_.date) } else { - // push join message only to joining user - seqUpdExt.deliverUserUpdate( - userId = cmd.joiningUserId, - update = serviceMessageUpdate( - cmd.joiningUserId, - dateMillis, - randomId, + // write service message only for joining user + // and push join message + for { + _ ← dialogExt.writeMessageSelf( + userId = cmd.joiningUserId, + peer = apiGroupPeer, + senderUserId = cmd.joiningUserId, + dateMillis = dateMillis, + randomId = randomId, serviceMessage - ), - deliveryTag = Some(Optimization.GroupV2) - ) map (_ ⇒ dateMillis) + ) + _ ← seqUpdExt.deliverUserUpdate( + userId = cmd.joiningUserId, + update = serviceMessageUpdate( + cmd.joiningUserId, + dateMillis, + randomId, + serviceMessage + ), + deliveryTag = Some(Optimization.GroupV2) + ) + } yield dateMillis } } yield SeqStateDate(seq, state, date) @@ -377,6 +389,7 @@ private[group] trait MemberCommandHandlers extends GroupsImplicits { ), deliveryTag = Some(Optimization.GroupV2) ) + _ ← dialogExt.bump(cmd.joiningUserId, apiGroupPeer.asModel) } yield SeqStateDate(seq, state, dateMillis) val result: Future[(SeqStateDate, Vector[Int], Long)] = diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/user/UserCommandHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/user/UserCommandHandlers.scala index 811147f640..b7204ee7fb 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/user/UserCommandHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/user/UserCommandHandlers.scala @@ -1,6 +1,6 @@ package im.actor.server.user -import java.time.{ LocalDateTime, ZoneOffset } +import java.time.{ Instant, LocalDateTime, ZoneOffset } import java.util.TimeZone import akka.actor.{ ActorSystem, Status } @@ -437,13 +437,13 @@ private[user] trait UserCommandHandlers { // TODO: DRY it, finally! private def markContactRegistered(user: UserState, phoneNumber: Long, isSilent: Boolean): Future[Unit] = { - val date = new DateTime + val dateMillis = Instant.now.toEpochMilli for { contacts ← db.run(UnregisteredPhoneContactRepo.find(phoneNumber)) _ = log.debug(s"Unregistered $phoneNumber is in contacts of users: $contacts") _ ← Future.sequence(contacts map { contact ⇒ val randomId = ThreadLocalSecureRandom.current().nextLong() - val updateContactRegistered = UpdateContactRegistered(user.id, isSilent, date.getMillis, randomId) + val updateContactRegistered = UpdateContactRegistered(user.id, isSilent, dateMillis, randomId) val updateContactsAdded = UpdateContactsAdded(Vector(user.id)) val localName = contact.name val serviceMessage = ServiceMessages.contactRegistered(user.id, localName.getOrElse(user.name)) @@ -463,7 +463,7 @@ private[user] trait UserCommandHandlers { contact.ownerUserId, ApiPeer(ApiPeerType.Private, user.id), user.id, - date, + dateMillis, randomId, serviceMessage ) @@ -476,14 +476,14 @@ private[user] trait UserCommandHandlers { } private def markContactRegistered(user: UserState, email: String, isSilent: Boolean): Future[Unit] = { - val date = new DateTime + val dateMillis = Instant.now.toEpochMilli for { _ ← userExt.hooks.beforeEmailContactRegistered.runAll(user.id, email) contacts ← db.run(UnregisteredEmailContactRepo.find(email)) _ = log.debug(s"Unregistered $email is in contacts of users: $contacts") _ ← Future.sequence(contacts.map { contact ⇒ val randomId = ThreadLocalSecureRandom.current().nextLong() - val updateContactRegistered = UpdateContactRegistered(user.id, isSilent, date.getMillis, randomId) + val updateContactRegistered = UpdateContactRegistered(user.id, isSilent, dateMillis, randomId) val updateContactsAdded = UpdateContactsAdded(Vector(user.id)) val localName = contact.name val serviceMessage = ServiceMessages.contactRegistered(user.id, localName.getOrElse(user.name)) @@ -503,7 +503,7 @@ private[user] trait UserCommandHandlers { contact.ownerUserId, ApiPeer(ApiPeerType.Private, user.id), user.id, - date, + dateMillis, randomId, serviceMessage ) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/user/UserProcessor.scala b/actor-server/actor-core/src/main/scala/im/actor/server/user/UserProcessor.scala index d0c2af3c7c..00cfb778b6 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/user/UserProcessor.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/user/UserProcessor.scala @@ -262,9 +262,6 @@ private[user] final class UserProcessor val msg = de.getAllFields.values.head msg match { - case dc: DialogCommand if dc.isInstanceOf[DialogCommands.SendMessage] || dc.isInstanceOf[DialogCommands.WriteMessageSelf] ⇒ - dialogRoot(state.internalExtensions) ! msg - handleDialogCommand(state)(dc) case dc: DialogCommand ⇒ handleDialogCommand(state)(dc) case dq: DialogQuery ⇒ handleDialogQuery(state)(dq) } diff --git a/actor-server/project/Build.scala b/actor-server/project/Build.scala index ab7842b941..b3035eab28 100644 --- a/actor-server/project/Build.scala +++ b/actor-server/project/Build.scala @@ -23,7 +23,13 @@ object Build extends sbt.Build with Versioning with Releasing with Packaging { organization := "im.actor.server", organizationHomepage := Some(url("https://actor.im")), resolvers ++= Resolvers.seq, - scalacOptions ++= Seq("-Yopt-warnings"), +// scalacOptions ++= Seq( +// "-Ywarn-unused", +// "-Ywarn-adapted-args", +// "-Ywarn-nullary-override", +// "-Ywarn-nullary-unit", +// "-Ywarn-value-discard" +// ), parallelExecution := true ) ++ Sonatype.sonatypeSettings From 6c8f542bbae25289e2b349f312ddbca2936247a6 Mon Sep 17 00:00:00 2001 From: rockjam Date: Wed, 3 Aug 2016 02:53:00 +0300 Subject: [PATCH 229/414] fix(server:dialog): handle queries when stashing to send message --- .../im/actor/server/dialog/DialogCommandHandlers.scala | 2 +- .../scala/im/actor/server/dialog/DialogExtension.scala | 10 +++++----- .../scala/im/actor/server/dialog/DialogProcessor.scala | 5 ++++- .../scala/im/actor/server/user/UserProcessor.scala | 4 ++-- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogCommandHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogCommandHandlers.scala index 21ae5faa7f..5dc25f5278 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogCommandHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogCommandHandlers.scala @@ -40,7 +40,7 @@ trait DialogCommandHandlers extends PeersImplicits with UserAcl { replyTo forward fail context.become(receiveCommand) unstashAll() - }: Receive) orElse reactions, discardOld = true) + }: Receive) orElse reactions orElse queries, discardOld = true) val optClientAuthId = sm.senderAuthId withValidAccessHash(sm.getDest, optClientAuthId, sm.accessHash) { diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogExtension.scala b/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogExtension.scala index 4d0ad5ea27..7cc3885297 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogExtension.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogExtension.scala @@ -218,7 +218,7 @@ final class DialogExtensionImpl(system: ActorSystem) extends DialogExtension wit // DialogRootOperations def unarchive(userId: Int, clientAuthId: Long, peer: Peer): Future[SeqState] = - withValidPeer(peer, userId, failed = Future.failed[SeqState](DialogErrors.MessageToSelf)) { + withValidPeer(peer, userId) { (userExt.processorRegion.ref ? UserEnvelope(userId) .withDialogRootEnvelope( @@ -228,7 +228,7 @@ final class DialogExtensionImpl(system: ActorSystem) extends DialogExtension wit } def archive(userId: Int, clientAuthId: Long, peer: Peer): Future[SeqState] = - withValidPeer(peer, userId, failed = Future.failed[SeqState](DialogErrors.MessageToSelf)) { + withValidPeer(peer, userId) { (userExt.processorRegion.ref ? UserEnvelope(userId) .withDialogRootEnvelope( @@ -238,7 +238,7 @@ final class DialogExtensionImpl(system: ActorSystem) extends DialogExtension wit } def favourite(userId: Int, clientAuthId: Long, peer: Peer): Future[SeqState] = - withValidPeer(peer, userId, failed = Future.failed[SeqState](DialogErrors.MessageToSelf)) { + withValidPeer(peer, userId) { (userExt.processorRegion.ref ? UserEnvelope(userId) .withDialogRootEnvelope( @@ -248,7 +248,7 @@ final class DialogExtensionImpl(system: ActorSystem) extends DialogExtension wit } def unfavourite(userId: Int, clientAuthId: Long, peer: Peer): Future[SeqState] = - withValidPeer(peer, userId, failed = Future.failed[SeqState](DialogErrors.MessageToSelf)) { + withValidPeer(peer, userId) { (userExt.processorRegion.ref ? UserEnvelope(userId) .withDialogRootEnvelope(DialogRootEnvelope().withUnfavourite(DialogRootCommands.Unfavourite(Some(peer), clientAuthId)))) @@ -263,7 +263,7 @@ final class DialogExtensionImpl(system: ActorSystem) extends DialogExtension wit } def bump(userId: Int, peer: Peer): Future[Unit] = - withValidPeer(peer, userId, failed = Future.failed[Unit](DialogErrors.MessageToSelf)) { + withValidPeer(peer, userId) { (userExt.processorRegion.ref ? UserEnvelope(userId) .withDialogRootEnvelope( diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogProcessor.scala b/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogProcessor.scala index 19c4f30eb6..720885dfaa 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogProcessor.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogProcessor.scala @@ -1,8 +1,8 @@ package im.actor.server.dialog import akka.actor._ -import akka.event.Logging import akka.http.scaladsl.util.FastFuture +import akka.pattern.pipe import akka.util.Timeout import com.github.benmanes.caffeine.cache.Cache import im.actor.api.rpc.misc.ApiExtension @@ -115,6 +115,9 @@ private[dialog] final class DialogProcessor(val userId: Int, val peer: Peer, ext case WriteMessageSelf(_, senderUserId, date, randomId, message) ⇒ writeMessageSelf(senderUserId, date, randomId, message) } + // queries that dialog can actually reply when sending messages + def queries: Receive = handleQuery andThen (_ pipeTo sender()) + /** * dialog owner invokes `dc` * destination should be `peer` and origin should be `selfPeer` diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/user/UserProcessor.scala b/actor-server/actor-core/src/main/scala/im/actor/server/user/UserProcessor.scala index 00cfb778b6..d3f97ff138 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/user/UserProcessor.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/user/UserProcessor.scala @@ -254,8 +254,8 @@ private[user] final class UserProcessor case query: GetLocalName ⇒ contacts.ref forward query case StopOffice ⇒ context stop self case ReceiveTimeout ⇒ context.parent ! ShardRegion.Passivate(stopMessage = StopOffice) - case e @ DialogRootEnvelope(query, command) ⇒ - val msg = e.getAllFields.values.head + case env: DialogRootEnvelope ⇒ + val msg = env.getAllFields.values.head (dialogRoot(state.internalExtensions) ? msg) pipeTo sender() case de: DialogEnvelope ⇒ From 6480b84ba7837845239f543554a1c3d2091a9298 Mon Sep 17 00:00:00 2001 From: rockjam Date: Wed, 3 Aug 2016 15:56:09 +0300 Subject: [PATCH 230/414] test(server): remove counters changed check --- .../im/actor/server/api/rpc/service/MessagingServiceSpec.scala | 3 --- 1 file changed, 3 deletions(-) diff --git a/actor-server/actor-tests/src/test/scala/im/actor/server/api/rpc/service/MessagingServiceSpec.scala b/actor-server/actor-tests/src/test/scala/im/actor/server/api/rpc/service/MessagingServiceSpec.scala index a0d59662b2..aa7148ab74 100644 --- a/actor-server/actor-tests/src/test/scala/im/actor/server/api/rpc/service/MessagingServiceSpec.scala +++ b/actor-server/actor-tests/src/test/scala/im/actor/server/api/rpc/service/MessagingServiceSpec.scala @@ -154,9 +154,6 @@ class MessagingServiceSpec implicit val clientData = clientData2 expectUpdate(classOf[UpdateChatGroupsChanged])(identity) expectUpdate(classOf[UpdateMessage])(identity) - expectUpdate(classOf[UpdateCountersChanged]) { upd ⇒ - upd.counters.globalCounter shouldEqual Some(1) - } } } } From cb86fc927eca09436879c2a940ae816d3dbb47a1 Mon Sep 17 00:00:00 2001 From: rockjam Date: Wed, 3 Aug 2016 16:28:59 +0300 Subject: [PATCH 231/414] fix(server:dialog): update grouped dialog list after message received --- .../scala/im/actor/server/dialog/DialogCommandHandlers.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogCommandHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogCommandHandlers.scala index 5dc25f5278..bd06349b2f 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogCommandHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogCommandHandlers.scala @@ -101,7 +101,6 @@ trait DialogCommandHandlers extends PeersImplicits with UserAcl { } (for { - _ ← dialogExt.bump(userId, peer) _ ← deliveryExt.receiverDelivery( receiverUserId = userId, senderUserId = sm.getOrigin.id, @@ -112,6 +111,7 @@ trait DialogCommandHandlers extends PeersImplicits with UserAcl { isFat = sm.isFat, deliveryTag = sm.deliveryTag ) + _ ← dialogExt.bump(userId, peer) } yield SendMessageAck()) pipeTo sender() deliveryExt.sendCountersUpdate(userId) From 6cb0b3ddbee00dadf6770e0219d1785a4bcfb9bc Mon Sep 17 00:00:00 2001 From: rockjam Date: Wed, 3 Aug 2016 19:13:55 +0300 Subject: [PATCH 232/414] fix(server:groups): put group id in invite json --- .../scala/im/actor/server/group/http/GroupsHttpHandler.scala | 2 +- .../src/main/scala/im/actor/server/api/http/json/models.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/http/GroupsHttpHandler.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/http/GroupsHttpHandler.scala index a34d0b4443..c369b9f72e 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/http/GroupsHttpHandler.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/http/GroupsHttpHandler.scala @@ -76,7 +76,7 @@ private[group] final class GroupsHttpHandler()(implicit system: ActorSystem) ext } } yield Right( json.GroupInviteInfo( - group = json.GroupInfo(groupTitle, groupAvatarUrls), + group = json.GroupInfo(groupId, groupTitle, groupAvatarUrls), inviter = optInviterInfo ) ) diff --git a/actor-server/actor-http-api/src/main/scala/im/actor/server/api/http/json/models.scala b/actor-server/actor-http-api/src/main/scala/im/actor/server/api/http/json/models.scala index fc84bec8a4..5ea4b6d282 100644 --- a/actor-server/actor-http-api/src/main/scala/im/actor/server/api/http/json/models.scala +++ b/actor-server/actor-http-api/src/main/scala/im/actor/server/api/http/json/models.scala @@ -5,7 +5,7 @@ case class Text(text: String) extends Content case class Image(imageUrl: String) extends Content case class Document(documentUrl: String) extends Content -case class GroupInfo(title: String, avatars: Option[AvatarUrls]) +case class GroupInfo(id: Int, title: String, avatars: Option[AvatarUrls]) case class InviterInfo(name: String, avatars: Option[AvatarUrls]) case class GroupInviteInfo(group: GroupInfo, inviter: Option[InviterInfo]) case class AvatarUrls(small: Option[String], large: Option[String], full: Option[String]) From edf854cbdd57c033b9ddcd50c18d813718a01256 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Wed, 3 Aug 2016 20:12:26 +0300 Subject: [PATCH 233/414] fix(android): recover join link --- .../android-sdk/src/main/AndroidManifest.xml | 34 ++---- .../sdk/controllers/root/RootActivity.java | 106 ++++++++++++++++++ .../src/main/res/values/ui_text.xml | 2 + 3 files changed, 118 insertions(+), 24 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/AndroidManifest.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/AndroidManifest.xml index 4f38c4ff30..aa775688df 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/AndroidManifest.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/AndroidManifest.xml @@ -67,51 +67,37 @@ android:label="@string/app_name" android:launchMode="singleTop" android:theme="@style/MainActivityTheme" - android:windowSoftInputMode="adjustPan" /> - - - + android:windowSoftInputMode="adjustPan"> - - - - - - - - - + + + diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/root/RootActivity.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/root/RootActivity.java index f71aa04f51..66ba582b6b 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/root/RootActivity.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/root/RootActivity.java @@ -1,14 +1,33 @@ package im.actor.sdk.controllers.root; +import android.content.DialogInterface; +import android.content.Intent; import android.graphics.drawable.ColorDrawable; import android.os.Bundle; import android.support.v4.app.Fragment; +import android.support.v7.app.AlertDialog; import android.support.v7.widget.Toolbar; +import android.widget.Toast; +import java.io.UnsupportedEncodingException; + +import im.actor.core.entity.Peer; +import im.actor.core.viewmodel.CommandCallback; +import im.actor.core.viewmodel.GroupVM; +import im.actor.runtime.HTTP; +import im.actor.runtime.android.AndroidContext; +import im.actor.runtime.function.Consumer; +import im.actor.runtime.http.HTTPResponse; +import im.actor.runtime.json.JSONException; +import im.actor.runtime.json.JSONObject; import im.actor.sdk.ActorSDK; import im.actor.sdk.R; +import im.actor.sdk.controllers.Intents; import im.actor.sdk.controllers.activity.BaseFragmentActivity; +import static im.actor.sdk.util.ActorSDKMessenger.groups; +import static im.actor.sdk.util.ActorSDKMessenger.messenger; + /** * Root Activity of Application */ @@ -38,5 +57,92 @@ protected void onCreate(Bundle savedInstanceState) { .add(R.id.root, fragment) .commit(); } + + handleIntent(getIntent()); + } + + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + handleIntent(intent); + } + + private void handleIntent(Intent intent) { + if (intent.getAction() != null && intent.getAction().equals(Intent.ACTION_VIEW) && intent.getData() != null) { + String joinGroupUrl = intent.getData().toString(); + if (joinGroupUrl != null && (joinGroupUrl.contains("join") || joinGroupUrl.contains("token"))) { + String[] urlSplit = null; + if (joinGroupUrl.contains("join")) { + urlSplit = joinGroupUrl.split("/join/"); + } else if (joinGroupUrl.contains("token")) { + urlSplit = joinGroupUrl.split("token="); + } + if (urlSplit != null) { + joinGroupUrl = urlSplit[urlSplit.length - 1]; + + final String token = joinGroupUrl; + HTTP.getMethod("https://api.actor.im/v1/groups/invites/" + joinGroupUrl, 0, 0, 0).then(new Consumer() { + @Override + public void apply(HTTPResponse httpResponse) { + try { + JSONObject data = new JSONObject(new String(httpResponse.getContent(), "UTF-8")); + JSONObject group = data.getJSONObject("group"); + String title = group.getString("title"); + if (group.has("id")) { + int gid = group.getInt("id"); + //Check if we have this group + try { + GroupVM groupVM = groups().get(gid); + if (groupVM.isMember().get()) { + //Have this group, is member, just open it + startActivity(Intents.openDialog(Peer.group(gid), false, RootActivity.this)); + } else { + //Have this group, but not member, join it + joinViaToken(token, title); + } + } catch (Exception e) { + //Do not have this group, join it + joinViaToken(token, title); + } + } else { + joinViaToken(token, title); + } + } catch (JSONException | UnsupportedEncodingException e) { + e.printStackTrace(); + } + } + }); + } + } + } + } + + private void joinViaToken(String joinGroupUrl, String title) { + AlertDialog.Builder b = new AlertDialog.Builder(this); + b.setTitle(getString(R.string.invite_link_join_confirm, title)) + .setPositiveButton(R.string.dialog_yes, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + dialogInterface.dismiss(); + execute(messenger().joinGroupViaToken(joinGroupUrl), R.string.invite_link_title, new CommandCallback() { + @Override + public void onResult(Integer res) { + startActivity(Intents.openGroupDialog(res, true, RootActivity.this)); + finish(); + } + + @Override + public void onError(Exception e) { + Toast.makeText(RootActivity.this, e.getMessage(), Toast.LENGTH_SHORT).show(); + } + }); + } + }) + .setNegativeButton(R.string.dialog_cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + dialogInterface.dismiss(); + } + }).show(); } } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml index be5628f390..5b735412e0 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml @@ -444,6 +444,8 @@ Unable to revoke invite link Loading invite link + Join "%1$s"? + Documents No documents yet From 789532e624bfce9209bc12ad1918080030c803a0 Mon Sep 17 00:00:00 2001 From: rockjam Date: Wed, 3 Aug 2016 21:56:47 +0300 Subject: [PATCH 234/414] chore(server): prepare 3.0.0 release --- actor-server/notes/3.0.0.markdown | 14 ++++++++++++++ actor-server/version.sbt | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 actor-server/notes/3.0.0.markdown diff --git a/actor-server/notes/3.0.0.markdown b/actor-server/notes/3.0.0.markdown new file mode 100644 index 0000000000..c4d82de026 --- /dev/null +++ b/actor-server/notes/3.0.0.markdown @@ -0,0 +1,14 @@ +### Release notes +* :shipit: a lot of new group features +* :bug: various bugfixes +* :rocket: server dependencies updates + +#### New groups :tada: +This release focused on group features, including: +* private and public channels; +* short names for public groups. Just like user can have nickname, group can have short names assigned by owner. Short name can be used to find group in global search, or invite people with nice invite url. +* shared history for public groups; +* multiple group admins; +* ownership transfer; +* fine grained permission settings for admins/members. Owner of group decides what members can/can't do; +* optimized performance for big groups. diff --git a/actor-server/version.sbt b/actor-server/version.sbt index e23b1fae2e..5051b75100 100644 --- a/actor-server/version.sbt +++ b/actor-server/version.sbt @@ -1 +1 @@ -version in ThisBuild := "1.0.154-SNAPSHOT" +version in ThisBuild := "3.0.0-SNAPSHOT" From 1f0239f58b6b4e676b6ac977c0097b05ddcda666 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Thu, 4 Aug 2016 14:33:25 +0300 Subject: [PATCH 235/414] fix(android): delete group after leave, fix group members binding --- .../src/main/java/im/actor/sdk/ActorSDK.java | 9 +++++ .../im/actor/sdk/controllers/ActorBinder.java | 14 +++++--- .../sdk/controllers/BinderCompatFragment.java | 8 +++-- .../controllers/group/GroupInfoFragment.java | 34 ++++++++++++++----- .../group/view/MembersAdapter.java | 9 +++-- .../sdk/controllers/root/RootActivity.java | 2 +- 6 files changed, 57 insertions(+), 19 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorSDK.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorSDK.java index 1ed3dc8afd..28b11dbf9f 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorSDK.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorSDK.java @@ -193,6 +193,7 @@ public class ActorSDK { */ private boolean callsEnabled = false; private boolean videoCallsEnabled = false; + private String inviteDataUrl = "https://api.actor.im/v1/groups/invites/"; private ActorSDK() { endpoints = new String[]{ @@ -1052,6 +1053,14 @@ public void setVideoCallsEnabled(boolean videoCallsEnabled) { this.videoCallsEnabled = videoCallsEnabled; } + public String getInviteDataUrl() { + return inviteDataUrl; + } + + public void setInviteDataUrl(String inviteDataUrl) { + this.inviteDataUrl = inviteDataUrl; + } + /** * Used for handling delegated ViewHolders */ diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/ActorBinder.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/ActorBinder.java index d65b31f22a..3f594adcb3 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/ActorBinder.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/ActorBinder.java @@ -250,27 +250,31 @@ public Binding bind(Value value, ValueChangedListener listener, boolea return b; } - public void bind(Value value, boolean notify, ValueChangedListener listener) { + public Binding bind(Value value, boolean notify, ValueChangedListener listener) { value.subscribe(listener, notify); - bindings.add(new Binding(value, listener)); + Binding binding = new Binding(value, listener); + bindings.add(binding); + return binding; } - public void bind(final Value value1, final Value value2, + public Binding[] bind(final Value value1, final Value value2, final ValueDoubleChangedListener listener) { - bind(value1, false, new ValueChangedListener() { + Binding[] bindings = new Binding[2]; + bindings[0] = bind(value1, false, new ValueChangedListener() { @Override public void onChanged(T val, Value Value) { listener.onChanged(val, Value, value2.get(), value2); } }); - bind(value2, false, new ValueChangedListener() { + bindings[1] = bind(value2, false, new ValueChangedListener() { @Override public void onChanged(V val, Value Value) { listener.onChanged(value1.get(), value1, val, Value); } }); listener.onChanged(value1.get(), value1, value2.get(), value2); + return bindings; } public void bind(final Value value1, final Value value2, final Value value3, diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/BinderCompatFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/BinderCompatFragment.java index f8a2f47987..6ec35f16c4 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/BinderCompatFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/BinderCompatFragment.java @@ -49,8 +49,8 @@ public void bind(ValueModel value, boolean notify, ValueChangedListener void bind(ValueModel value1, ValueModel value2, ValueDoubleChangedListener listener) { - BINDER.bind(value1, value2, listener); + public ActorBinder.Binding[] bind(ValueModel value1, ValueModel value2, ValueDoubleChangedListener listener) { + return BINDER.bind(value1, value2, listener); } public void bind(final CoverAvatarView avatarView, final ValueModel avatar) { @@ -106,6 +106,10 @@ public void onDestroyView() { } } + protected ActorBinder getBINDER() { + return BINDER; + } + public void unbind(ActorBinder.Binding b) { BINDER.unbind(b); } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java index 4a1b336467..e46e26b9be 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java @@ -35,6 +35,7 @@ import im.actor.sdk.ActorSDKLauncher; import im.actor.sdk.ActorStyle; import im.actor.sdk.R; +import im.actor.sdk.controllers.ActorBinder; import im.actor.sdk.controllers.Intents; import im.actor.sdk.controllers.activity.BaseActivity; import im.actor.sdk.controllers.BaseFragment; @@ -53,6 +54,7 @@ public class GroupInfoFragment extends BaseFragment { private static final String EXTRA_CHAT_ID = "chat_id"; + private ActorBinder.Binding[] memberBindings; public static GroupInfoFragment create(int chatId) { Bundle args = new Bundle(); @@ -256,7 +258,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa .setMessage(getString(R.string.alert_leave_group_message).replace("%1$s", groupVM.getName().get())) .setPositiveButton(R.string.alert_leave_group_yes, (dialog2, which) -> { - execute(messenger().leaveGroup(chatId)); + execute(messenger().leaveAndDeleteGroup(chatId)); }) .setNegativeButton(R.string.dialog_cancel, null) .show() @@ -275,13 +277,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa // groupUserAdapter = new MembersAdapter(getActivity(), getArguments().getInt("groupId")); - bind(groupVM.getIsAsyncMembers(), groupVM.getMembers(), (isAsyncMembers, valueModel, memberList, valueModel2) -> { - if (isAsyncMembers) { - groupUserAdapter.setMembers(new ArrayList<>()); - } else { - groupUserAdapter.setMembers(memberList); - } - }); + listView.setAdapter(groupUserAdapter); listView.setOnItemClickListener((parent, view, position, id) -> { Object item = parent.getItemAtPosition(position); @@ -426,6 +422,28 @@ public void onError(Exception e) { .setCanceledOnTouchOutside(true); } + @Override + public void onResume() { + super.onResume(); + memberBindings = bind(groupVM.getIsAsyncMembers(), groupVM.getMembers(), (isAsyncMembers, valueModel, memberList, valueModel2) -> { + if (isAsyncMembers) { + groupUserAdapter.setMembers(new ArrayList<>()); + } else { + groupUserAdapter.setMembers(memberList); + } + }); + } + + @Override + public void onPause() { + super.onPause(); + if (memberBindings != null) { + for (ActorBinder.Binding b : memberBindings) { + getBINDER().unbind(b); + } + } + } + @Override public void onCreateOptionsMenu(Menu menu, MenuInflater menuInflater) { super.onCreateOptionsMenu(menu, menuInflater); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/view/MembersAdapter.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/view/MembersAdapter.java index e2f9109706..e5e75b74c1 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/view/MembersAdapter.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/view/MembersAdapter.java @@ -40,10 +40,13 @@ public MembersAdapter(Context context, int groupId) { } public void setMembers(Collection members) { - setMembers(members, true); + setMembers(members, true, true); } - public void setMembers(Collection members, boolean sort) { + public void setMembers(Collection members, boolean clear, boolean sort) { + if (clear) { + this.members.clear(); + } if (sort) { GroupMember[] membersArray = members.toArray(new GroupMember[members.size()]); Arrays.sort(membersArray, (a, b) -> { @@ -102,7 +105,7 @@ private void loadMore() { nextMembers = groupMembersSlice.getNext(); loaddedToEnd = nextMembers == null; loadInProgress = false; - setMembers(groupMembersSlice.getMembers(), false); + setMembers(groupMembersSlice.getMembers(), false, false); }); } } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/root/RootActivity.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/root/RootActivity.java index 66ba582b6b..11b9eb5405 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/root/RootActivity.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/root/RootActivity.java @@ -81,7 +81,7 @@ private void handleIntent(Intent intent) { joinGroupUrl = urlSplit[urlSplit.length - 1]; final String token = joinGroupUrl; - HTTP.getMethod("https://api.actor.im/v1/groups/invites/" + joinGroupUrl, 0, 0, 0).then(new Consumer() { + HTTP.getMethod(ActorSDK.sharedActor().getInviteDataUrl() + joinGroupUrl, 0, 0, 0).then(new Consumer() { @Override public void apply(HTTPResponse httpResponse) { try { From a1f5dcdd37235a9a77a4d338b5ba94d499a155e1 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Thu, 4 Aug 2016 17:08:35 +0300 Subject: [PATCH 236/414] chore(android): handle invite url in InviteHandler --- .../sdk/controllers/root/RootActivity.java | 85 +------------- .../sdk/controllers/tools/InviteHandler.java | 106 ++++++++++++++++++ 2 files changed, 111 insertions(+), 80 deletions(-) create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/tools/InviteHandler.java diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/root/RootActivity.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/root/RootActivity.java index 11b9eb5405..b68f1cc510 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/root/RootActivity.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/root/RootActivity.java @@ -24,6 +24,9 @@ import im.actor.sdk.R; import im.actor.sdk.controllers.Intents; import im.actor.sdk.controllers.activity.BaseFragmentActivity; +import im.actor.sdk.controllers.tools.InviteHandler; +import im.actor.sdk.intents.ActorIntent; +import im.actor.sdk.intents.ActorIntentActivity; import static im.actor.sdk.util.ActorSDKMessenger.groups; import static im.actor.sdk.util.ActorSDKMessenger.messenger; @@ -58,91 +61,13 @@ protected void onCreate(Bundle savedInstanceState) { .commit(); } - handleIntent(getIntent()); + InviteHandler.handleIntent(this, getIntent()); } @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); - handleIntent(intent); + InviteHandler.handleIntent(this, intent); } - private void handleIntent(Intent intent) { - if (intent.getAction() != null && intent.getAction().equals(Intent.ACTION_VIEW) && intent.getData() != null) { - String joinGroupUrl = intent.getData().toString(); - if (joinGroupUrl != null && (joinGroupUrl.contains("join") || joinGroupUrl.contains("token"))) { - String[] urlSplit = null; - if (joinGroupUrl.contains("join")) { - urlSplit = joinGroupUrl.split("/join/"); - } else if (joinGroupUrl.contains("token")) { - urlSplit = joinGroupUrl.split("token="); - } - if (urlSplit != null) { - joinGroupUrl = urlSplit[urlSplit.length - 1]; - - final String token = joinGroupUrl; - HTTP.getMethod(ActorSDK.sharedActor().getInviteDataUrl() + joinGroupUrl, 0, 0, 0).then(new Consumer() { - @Override - public void apply(HTTPResponse httpResponse) { - try { - JSONObject data = new JSONObject(new String(httpResponse.getContent(), "UTF-8")); - JSONObject group = data.getJSONObject("group"); - String title = group.getString("title"); - if (group.has("id")) { - int gid = group.getInt("id"); - //Check if we have this group - try { - GroupVM groupVM = groups().get(gid); - if (groupVM.isMember().get()) { - //Have this group, is member, just open it - startActivity(Intents.openDialog(Peer.group(gid), false, RootActivity.this)); - } else { - //Have this group, but not member, join it - joinViaToken(token, title); - } - } catch (Exception e) { - //Do not have this group, join it - joinViaToken(token, title); - } - } else { - joinViaToken(token, title); - } - } catch (JSONException | UnsupportedEncodingException e) { - e.printStackTrace(); - } - } - }); - } - } - } - } - - private void joinViaToken(String joinGroupUrl, String title) { - AlertDialog.Builder b = new AlertDialog.Builder(this); - b.setTitle(getString(R.string.invite_link_join_confirm, title)) - .setPositiveButton(R.string.dialog_yes, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - dialogInterface.dismiss(); - execute(messenger().joinGroupViaToken(joinGroupUrl), R.string.invite_link_title, new CommandCallback() { - @Override - public void onResult(Integer res) { - startActivity(Intents.openGroupDialog(res, true, RootActivity.this)); - finish(); - } - - @Override - public void onError(Exception e) { - Toast.makeText(RootActivity.this, e.getMessage(), Toast.LENGTH_SHORT).show(); - } - }); - } - }) - .setNegativeButton(R.string.dialog_cancel, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - dialogInterface.dismiss(); - } - }).show(); - } } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/tools/InviteHandler.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/tools/InviteHandler.java new file mode 100644 index 0000000000..243b7ff444 --- /dev/null +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/tools/InviteHandler.java @@ -0,0 +1,106 @@ +package im.actor.sdk.controllers.tools; + +import android.content.DialogInterface; +import android.content.Intent; +import android.support.v7.app.AlertDialog; +import android.widget.Toast; + +import java.io.UnsupportedEncodingException; + +import im.actor.core.entity.Peer; +import im.actor.core.viewmodel.CommandCallback; +import im.actor.core.viewmodel.GroupVM; +import im.actor.runtime.HTTP; +import im.actor.runtime.function.Consumer; +import im.actor.runtime.http.HTTPResponse; +import im.actor.runtime.json.JSONException; +import im.actor.runtime.json.JSONObject; +import im.actor.sdk.ActorSDK; +import im.actor.sdk.R; +import im.actor.sdk.controllers.Intents; +import im.actor.sdk.controllers.activity.BaseActivity; + +import static im.actor.sdk.util.ActorSDKMessenger.groups; +import static im.actor.sdk.util.ActorSDKMessenger.messenger; + +public class InviteHandler { + + public static void handleIntent(BaseActivity activity, Intent intent) { + if (intent.getAction() != null && intent.getAction().equals(Intent.ACTION_VIEW) && intent.getData() != null) { + String joinGroupUrl = intent.getData().toString(); + if (joinGroupUrl != null && (joinGroupUrl.contains("join") || joinGroupUrl.contains("token"))) { + String[] urlSplit = null; + if (joinGroupUrl.contains("join")) { + urlSplit = joinGroupUrl.split("/join/"); + } else if (joinGroupUrl.contains("token")) { + urlSplit = joinGroupUrl.split("token="); + } + if (urlSplit != null) { + joinGroupUrl = urlSplit[urlSplit.length - 1]; + + final String token = joinGroupUrl; + HTTP.getMethod(ActorSDK.sharedActor().getInviteDataUrl() + joinGroupUrl, 0, 0, 0).then(new Consumer() { + @Override + public void apply(HTTPResponse httpResponse) { + try { + JSONObject data = new JSONObject(new String(httpResponse.getContent(), "UTF-8")); + JSONObject group = data.getJSONObject("group"); + String title = group.getString("title"); + if (group.has("id")) { + int gid = group.getInt("id"); + //Check if we have this group + try { + GroupVM groupVM = groups().get(gid); + if (groupVM.isMember().get()) { + //Have this group, is member, just open it + activity.startActivity(Intents.openDialog(Peer.group(gid), false, activity)); + } else { + //Have this group, but not member, join it + joinViaToken(token, title, activity); + } + } catch (Exception e) { + //Do not have this group, join it + joinViaToken(token, title, activity); + } + } else { + joinViaToken(token, title, activity); + } + } catch (JSONException | UnsupportedEncodingException e) { + e.printStackTrace(); + } + } + }); + } + } + } + } + + private static void joinViaToken(String joinGroupUrl, String title, BaseActivity activity) { + AlertDialog.Builder b = new AlertDialog.Builder(activity); + b.setTitle(activity.getString(R.string.invite_link_join_confirm, title)) + .setPositiveButton(R.string.dialog_yes, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + dialogInterface.dismiss(); + activity.execute(messenger().joinGroupViaToken(joinGroupUrl), R.string.invite_link_title, new CommandCallback() { + @Override + public void onResult(Integer res) { + activity.startActivity(Intents.openGroupDialog(res, true, activity)); + } + + @Override + public void onError(Exception e) { + Toast.makeText(activity, e.getMessage(), Toast.LENGTH_SHORT).show(); + } + }); + } + + }) + .setNegativeButton(R.string.dialog_cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + dialogInterface.dismiss(); + } + }).show(); + } +} From d4015138c0485b23528410ab64e9311016b3d14e Mon Sep 17 00:00:00 2001 From: kor_ka Date: Thu, 4 Aug 2016 18:35:56 +0300 Subject: [PATCH 237/414] feat(android): add autocomplete and quote fragments to delegate --- .../java/im/actor/sdk/ActorSDKDelegate.java | 19 +++++++++++++++++++ .../im/actor/sdk/BaseActorSDKDelegate.java | 12 ++++++++++++ .../conversation/ChatFragment.java | 14 ++++++++++++-- .../mentions/AutocompleteFragment.java | 6 ++++-- 4 files changed, 47 insertions(+), 4 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorSDKDelegate.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorSDKDelegate.java index a1fff73cea..f79148c74e 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorSDKDelegate.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorSDKDelegate.java @@ -11,8 +11,10 @@ import im.actor.sdk.controllers.conversation.ChatFragment; import im.actor.sdk.controllers.conversation.attach.AbsAttachFragment; import im.actor.sdk.controllers.conversation.inputbar.InputBarFragment; +import im.actor.sdk.controllers.conversation.mentions.AutocompleteFragment; import im.actor.sdk.controllers.conversation.messages.content.MessageHolder; import im.actor.sdk.controllers.conversation.messages.MessagesAdapter; +import im.actor.sdk.controllers.conversation.quote.QuoteFragment; import im.actor.sdk.controllers.settings.BaseGroupInfoActivity; import im.actor.sdk.intents.ActorIntent; import im.actor.sdk.intents.ActorIntentFragmentActivity; @@ -98,6 +100,21 @@ public interface ActorSDKDelegate { @Nullable InputBarFragment fragmentForChatInput(); + /** + * If Not null returned, overrides chat autocomplete fragment + * + * @return Custom chat autocomplete fragment + * @param peer peer + */ + AutocompleteFragment fragmentForAutocomplete(Peer peer); + + /** + * If Not null returned, overrides chat quote fragment + * + * @return Custom chat quote fragment + */ + QuoteFragment fragmentForQuote(); + /** * If Not null returned, overrides default toolbar (no-ui) fragment * @@ -201,4 +218,6 @@ public interface ActorSDKDelegate { * @return notification sound color */ int getNotificationColor(); + + } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/BaseActorSDKDelegate.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/BaseActorSDKDelegate.java index 8bb53d488b..ad319c7bb7 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/BaseActorSDKDelegate.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/BaseActorSDKDelegate.java @@ -12,8 +12,10 @@ import im.actor.sdk.controllers.conversation.ChatFragment; import im.actor.sdk.controllers.conversation.attach.AbsAttachFragment; import im.actor.sdk.controllers.conversation.inputbar.InputBarFragment; +import im.actor.sdk.controllers.conversation.mentions.AutocompleteFragment; import im.actor.sdk.controllers.conversation.messages.content.MessageHolder; import im.actor.sdk.controllers.conversation.messages.MessagesAdapter; +import im.actor.sdk.controllers.conversation.quote.QuoteFragment; import im.actor.sdk.controllers.settings.BaseGroupInfoActivity; import im.actor.sdk.intents.ActorIntent; import im.actor.sdk.intents.ActorIntentFragmentActivity; @@ -74,6 +76,16 @@ public InputBarFragment fragmentForChatInput() { return null; } + @Override + public AutocompleteFragment fragmentForAutocomplete(Peer peer) { + return null; + } + + @Override + public QuoteFragment fragmentForQuote() { + return null; + } + @Nullable @Override public Fragment fragmentForToolbar(Peer peer) { diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatFragment.java index e41470f3a6..8bde15139e 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatFragment.java @@ -101,13 +101,23 @@ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, if (inputBarFragment == null) { inputBarFragment = new InputBarFragment(); } + + AutocompleteFragment autocompleteFragment = ActorSDK.sharedActor().getDelegate().fragmentForAutocomplete(peer); + if (autocompleteFragment == null) { + autocompleteFragment = AutocompleteFragment.create(peer); + } + + QuoteFragment quoteFragment = ActorSDK.sharedActor().getDelegate().fragmentForQuote(); + if (quoteFragment == null) { + quoteFragment = new QuoteFragment(); + } getChildFragmentManager().beginTransaction() .add(toolbarFragment, "toolbar") .add(R.id.messagesFragment, MessagesDefaultFragment.create(peer)) .add(R.id.sendFragment, inputBarFragment) - .add(R.id.quoteFragment, new QuoteFragment()) + .add(R.id.quoteFragment, quoteFragment) .add(R.id.emptyPlaceholder, new EmptyChatPlaceholder()) - .add(R.id.autocompleteContainer, new AutocompleteFragment(peer)) + .add(R.id.autocompleteContainer, autocompleteFragment) .commitNow(); AbsAttachFragment fragment = ActorSDK.sharedActor().getDelegate().fragmentForAttachMenu(peer); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/mentions/AutocompleteFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/mentions/AutocompleteFragment.java index 57d0eaf3c9..ff37afe9b0 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/mentions/AutocompleteFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/mentions/AutocompleteFragment.java @@ -34,10 +34,12 @@ public class AutocompleteFragment extends BaseFragment { private HolderAdapter autocompleteAdapter; private RecyclerListView autocompleteList; - public AutocompleteFragment(Peer peer) { + public static AutocompleteFragment create(Peer peer) { + AutocompleteFragment res = new AutocompleteFragment(); Bundle bundle = new Bundle(); bundle.putLong("peer", peer.getUnuqueId()); - setArguments(bundle); + res.setArguments(bundle); + return res; } public AutocompleteFragment() { From 2da86d43ea5da9ef8d6459c9c6a6728494fed8da Mon Sep 17 00:00:00 2001 From: Alashov Berkeli Date: Fri, 5 Aug 2016 10:51:24 +0500 Subject: [PATCH 238/414] feat(android): custom notification sound for global and per peer added multiple view visibility changer to ViewUtils --- .../im/actor/sdk/BaseActorSDKDelegate.java | 30 ++++- .../controllers/profile/ProfileFragment.java | 81 ++++++++++++- .../settings/NotificationsFragment.java | 104 +++++++++++++--- .../java/im/actor/sdk/util/ViewUtils.java | 47 +++++++- .../res/layout/fr_settings_notifications.xml | 112 ++++++++++++------ .../src/main/res/layout/fragment_profile.xml | 24 ++++ .../src/main/res/values/ui_text.xml | 8 +- 7 files changed, 338 insertions(+), 68 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/BaseActorSDKDelegate.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/BaseActorSDKDelegate.java index ad319c7bb7..c761660bbe 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/BaseActorSDKDelegate.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/BaseActorSDKDelegate.java @@ -1,5 +1,7 @@ package im.actor.sdk; +import android.content.Context; +import android.content.SharedPreferences; import android.net.Uri; import android.provider.Settings; import android.support.v4.app.Fragment; @@ -8,15 +10,15 @@ import org.jetbrains.annotations.Nullable; import im.actor.core.entity.Peer; +import im.actor.runtime.android.AndroidContext; import im.actor.runtime.android.view.BindedViewHolder; import im.actor.sdk.controllers.conversation.ChatFragment; import im.actor.sdk.controllers.conversation.attach.AbsAttachFragment; import im.actor.sdk.controllers.conversation.inputbar.InputBarFragment; import im.actor.sdk.controllers.conversation.mentions.AutocompleteFragment; -import im.actor.sdk.controllers.conversation.messages.content.MessageHolder; import im.actor.sdk.controllers.conversation.messages.MessagesAdapter; +import im.actor.sdk.controllers.conversation.messages.content.MessageHolder; import im.actor.sdk.controllers.conversation.quote.QuoteFragment; -import im.actor.sdk.controllers.settings.BaseGroupInfoActivity; import im.actor.sdk.intents.ActorIntent; import im.actor.sdk.intents.ActorIntentFragmentActivity; @@ -96,7 +98,7 @@ public Fragment fragmentForToolbar(Peer peer) { public ActorIntentFragmentActivity getSettingsIntent() { return null; } - + @Override public ActorIntentFragmentActivity getChatSettingsIntent() { return null; @@ -123,6 +125,17 @@ public MessageHolder getCustomMessageViewHolder(int dataTypeHash, MessagesAdapte } public Uri getNotificationSoundForPeer(Peer peer) { + SharedPreferences sharedPreferences = AndroidContext.getContext().getSharedPreferences("notifications", Context.MODE_PRIVATE); + + String globalSound = sharedPreferences.getString("userSound_" + peer.getPeerId(), null); + if (globalSound != null) { + if (globalSound.equals("none")) { + return null; + } else { + return Uri.parse(globalSound); + } + } + return getNotificationSound(); } @@ -131,6 +144,17 @@ public int getNotificationColorForPeer(Peer peer) { } public Uri getNotificationSound() { + SharedPreferences sharedPreferences = AndroidContext.getContext().getSharedPreferences("notifications", Context.MODE_PRIVATE); + + String globalSound = sharedPreferences.getString("globalSound", null); + if (globalSound != null) { + if (globalSound.equals("none")) { + return null; + } else { + return Uri.parse(globalSound); + } + } + return Settings.System.DEFAULT_NOTIFICATION_URI; } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/profile/ProfileFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/profile/ProfileFragment.java index 43ff205c73..d44fcefb8c 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/profile/ProfileFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/profile/ProfileFragment.java @@ -1,14 +1,18 @@ package im.actor.sdk.controllers.profile; +import android.app.Activity; import android.content.ClipData; import android.content.ClipboardManager; import android.content.Context; import android.content.Intent; +import android.content.SharedPreferences; import android.graphics.Color; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; +import android.media.RingtoneManager; import android.net.Uri; import android.os.Bundle; +import android.provider.Settings; import android.support.design.widget.Snackbar; import android.support.v4.graphics.drawable.DrawableCompat; import android.support.v7.app.ActionBar; @@ -32,25 +36,31 @@ import java.util.ArrayList; +import im.actor.core.entity.Peer; import im.actor.core.viewmodel.UserEmail; +import im.actor.core.viewmodel.UserPhone; +import im.actor.core.viewmodel.UserVM; import im.actor.runtime.mvvm.Value; import im.actor.runtime.mvvm.ValueChangedListener; import im.actor.sdk.ActorSDK; import im.actor.sdk.R; +import im.actor.sdk.controllers.BaseFragment; import im.actor.sdk.controllers.Intents; import im.actor.sdk.controllers.fragment.preview.ViewAvatarActivity; -import im.actor.sdk.controllers.BaseFragment; import im.actor.sdk.util.Screen; +import im.actor.sdk.util.ViewUtils; import im.actor.sdk.view.avatar.AvatarView; -import im.actor.core.entity.Peer; -import im.actor.core.viewmodel.UserPhone; -import im.actor.core.viewmodel.UserVM; import static im.actor.sdk.util.ActorSDKMessenger.messenger; import static im.actor.sdk.util.ActorSDKMessenger.users; public class ProfileFragment extends BaseFragment { + private SharedPreferences notificationPreferences; + private SharedPreferences.Editor notificationPreferencesEditor; + + public static int SOUND_PICKER_REQUEST_CODE = 122; + public static final String EXTRA_UID = "uid"; public static ProfileFragment create(int uid) { @@ -406,16 +416,61 @@ public void onChanged(final String newUserName, Value valueModel) { // Notifications // View notificationContainer = res.findViewById(R.id.notificationsCont); + View notificationPickerContainer = res.findViewById(R.id.notificationsPickerCont); + ((TextView) notificationContainer.findViewById(R.id.settings_notifications_title)).setTextColor(style.getTextPrimaryColor()); final SwitchCompat notificationEnable = (SwitchCompat) res.findViewById(R.id.enableNotifications); - notificationEnable.setChecked(messenger().isNotificationsEnabled(Peer.user(user.getId()))); - notificationEnable.setOnCheckedChangeListener((buttonView, isChecked) -> messenger().changeNotificationsEnabled(Peer.user(user.getId()), isChecked)); + Peer peer = Peer.user(user.getId()); + notificationEnable.setChecked(messenger().isNotificationsEnabled(peer)); + if (messenger().isNotificationsEnabled(peer)) { + ViewUtils.showView(notificationPickerContainer, false); + } else { + ViewUtils.goneView(notificationPickerContainer, false); + } + notificationEnable.setOnCheckedChangeListener((buttonView, isChecked) -> { + messenger().changeNotificationsEnabled(Peer.user(user.getId()), isChecked); + + if (isChecked) { + ViewUtils.showView(notificationPickerContainer, false); + } else { + ViewUtils.goneView(notificationPickerContainer, false); + } + }); notificationContainer.setOnClickListener(v -> notificationEnable.setChecked(!notificationEnable.isChecked())); ImageView iconView = (ImageView) res.findViewById(R.id.settings_notification_icon); Drawable drawable = DrawableCompat.wrap(getResources().getDrawable(R.drawable.ic_list_black_24dp)); DrawableCompat.setTint(drawable, style.getSettingsIconColor()); iconView.setImageDrawable(drawable); + notificationPreferences = getActivity().getSharedPreferences("notifications", Context.MODE_PRIVATE); + notificationPreferencesEditor = notificationPreferences.edit(); + + ((TextView) notificationPickerContainer.findViewById(R.id.settings_notifications_picker_title)).setTextColor(style.getTextPrimaryColor()); + notificationPickerContainer.setOnClickListener(view -> { + Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER); + intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_NOTIFICATION); + intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true); + intent.putExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI, RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)); + + Uri currentSound = null; + String defaultPath = null; + Uri defaultUri = Settings.System.DEFAULT_NOTIFICATION_URI; + if (defaultUri != null) { + defaultPath = defaultUri.getPath(); + } + + String path = notificationPreferences.getString("userSound_" + uid, defaultPath); + if (path != null && !path.equals("none")) { + if (path.equals(defaultPath)) { + currentSound = defaultUri; + } else { + currentSound = Uri.parse(path); + } + } + intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, currentSound); + startActivityForResult(intent, SOUND_PICKER_REQUEST_CODE); + }); + // // Block // @@ -459,6 +514,20 @@ public void onChanged(final String newUserName, Value valueModel) { return res; } + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (resultCode == Activity.RESULT_OK && requestCode == SOUND_PICKER_REQUEST_CODE) { + Uri ringtone = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI); + + if (ringtone != null) { + notificationPreferencesEditor.putString("userSound_" + uid, ringtone.toString()); + } else { + notificationPreferencesEditor.putString("userSound" + uid, "none"); + } + notificationPreferencesEditor.commit(); + } + } + @Override public void onResume() { super.onResume(); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/NotificationsFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/NotificationsFragment.java index df28494ca0..419ead7629 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/NotificationsFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/NotificationsFragment.java @@ -1,6 +1,13 @@ package im.actor.sdk.controllers.settings; +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.media.RingtoneManager; +import android.net.Uri; import android.os.Bundle; +import android.provider.Settings; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -11,11 +18,17 @@ import im.actor.sdk.ActorStyle; import im.actor.sdk.R; import im.actor.sdk.controllers.BaseFragment; +import im.actor.sdk.util.ViewUtils; import static im.actor.sdk.util.ActorSDKMessenger.messenger; public class NotificationsFragment extends BaseFragment { + private SharedPreferences notificationPreferences; + private SharedPreferences.Editor notificationPreferencesEditor; + + public static int SOUND_PICKER_REQUEST_CODE = 122; + @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View res = inflater.inflate(R.layout.fr_settings_notifications, container, false); @@ -25,6 +38,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa res.findViewById(R.id.divider2).setBackgroundColor(ActorSDK.sharedActor().style.getDividerColor()); res.findViewById(R.id.divider3).setBackgroundColor(ActorSDK.sharedActor().style.getDividerColor()); res.findViewById(R.id.divider4).setBackgroundColor(ActorSDK.sharedActor().style.getDividerColor()); + res.findViewById(R.id.divider5).setBackgroundColor(ActorSDK.sharedActor().style.getDividerColor()); // Conversation tone final CheckBox enableTones = (CheckBox) res.findViewById(R.id.enableConversationTones); @@ -42,20 +56,6 @@ public void onClick(View v) { enableTones.setOnClickListener(enableTonesListener); res.findViewById(R.id.conversationTonesCont).setOnClickListener(enableTonesListener); - // Sound - final CheckBox enableSound = (CheckBox) res.findViewById(R.id.enableSound); - enableSound.setChecked(messenger().isNotificationSoundEnabled()); - View.OnClickListener enableSoundListener = new View.OnClickListener() { - @Override - public void onClick(View v) { - messenger().changeNotificationSoundEnabled(!messenger().isNotificationSoundEnabled()); - enableSound.setChecked(messenger().isNotificationSoundEnabled()); - } - }; - enableSound.setOnClickListener(enableSoundListener); - res.findViewById(R.id.soundCont).setOnClickListener(enableSoundListener); - ((TextView) res.findViewById(R.id.settings_sound_title)).setTextColor(style.getTextPrimaryColor()); - ((TextView) res.findViewById(R.id.settings_sound_hint)).setTextColor(style.getTextSecondaryColor()); // Vibration final CheckBox enableVibration = (CheckBox) res.findViewById(R.id.enableVibration); enableVibration.setChecked(messenger().isNotificationVibrationEnabled()); @@ -70,6 +70,7 @@ public void onClick(View v) { res.findViewById(R.id.vibrationCont).setOnClickListener(enableVibrationListener); ((TextView) res.findViewById(R.id.settings_vibration_title)).setTextColor(style.getTextPrimaryColor()); ((TextView) res.findViewById(R.id.settings_vibration_hint)).setTextColor(style.getTextSecondaryColor()); + // Group final CheckBox enableGroup = (CheckBox) res.findViewById(R.id.enableGroup); enableGroup.setChecked(messenger().isGroupNotificationsEnabled()); @@ -84,6 +85,7 @@ public void onClick(View v) { res.findViewById(R.id.groupCont).setOnClickListener(enableGroupListener); ((TextView) res.findViewById(R.id.settings_group_title)).setTextColor(style.getTextPrimaryColor()); ((TextView) res.findViewById(R.id.settings_group_hint)).setTextColor(style.getTextSecondaryColor()); + // Mentions final CheckBox enableGroupMentions = (CheckBox) res.findViewById(R.id.enableGroupMentions); enableGroupMentions.setChecked(messenger().isGroupNotificationsOnlyMentionsEnabled()); @@ -98,8 +100,8 @@ public void onClick(View v) { res.findViewById(R.id.groupMentionsCont).setOnClickListener(enableGroupMentionsListener); ((TextView) res.findViewById(R.id.settings_group_mentions_title)).setTextColor(style.getTextPrimaryColor()); ((TextView) res.findViewById(R.id.settings_group_mentions_hint)).setTextColor(style.getTextSecondaryColor()); - // Names and messages + // Names and messages final CheckBox enableText = (CheckBox) res.findViewById(R.id.enableTitles); enableText.setChecked(messenger().isShowNotificationsText()); View.OnClickListener enableTextListener = new View.OnClickListener() { @@ -113,6 +115,78 @@ public void onClick(View v) { res.findViewById(R.id.titlesCont).setOnClickListener(enableTextListener); ((TextView) res.findViewById(R.id.settings_titles_title)).setTextColor(style.getTextPrimaryColor()); ((TextView) res.findViewById(R.id.settings_titles_hint)).setTextColor(style.getTextSecondaryColor()); + + // Sound + final CheckBox enableSound = (CheckBox) res.findViewById(R.id.enableSound); + enableSound.setChecked(messenger().isNotificationSoundEnabled()); + View.OnClickListener enableSoundListener = new View.OnClickListener() { + @Override + public void onClick(View v) { + messenger().changeNotificationSoundEnabled(!messenger().isNotificationSoundEnabled()); + enableSound.setChecked(messenger().isNotificationSoundEnabled()); + + //show/hide sound picker + View cont = res.findViewById(R.id.soundPickerCont); + View div = res.findViewById(R.id.divider5); + if (messenger().isNotificationSoundEnabled()) { + ViewUtils.showViews(cont, div); + } else { + ViewUtils.goneViews(cont, div); + } + } + }; + enableSound.setOnClickListener(enableSoundListener); + res.findViewById(R.id.soundCont).setOnClickListener(enableSoundListener); + ((TextView) res.findViewById(R.id.settings_sound_title)).setTextColor(style.getTextPrimaryColor()); + ((TextView) res.findViewById(R.id.settings_sound_hint)).setTextColor(style.getTextSecondaryColor()); + + // Sound picker + notificationPreferences = getActivity().getSharedPreferences("notifications", Context.MODE_PRIVATE); + notificationPreferencesEditor = notificationPreferences.edit(); + View.OnClickListener soundPickerListener = new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER); + intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_NOTIFICATION); + intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true); + intent.putExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI, RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)); + + Uri currentSound = null; + String defaultPath = null; + Uri defaultUri = Settings.System.DEFAULT_NOTIFICATION_URI; + if (defaultUri != null) { + defaultPath = defaultUri.getPath(); + } + + String path = notificationPreferences.getString("globalSound", defaultPath); + if (path != null && !path.equals("none")) { + if (path.equals(defaultPath)) { + currentSound = defaultUri; + } else { + currentSound = Uri.parse(path); + } + } + intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, currentSound); + startActivityForResult(intent, SOUND_PICKER_REQUEST_CODE); + } + }; + res.findViewById(R.id.soundPickerCont).setOnClickListener(soundPickerListener); + ((TextView) res.findViewById(R.id.settings_sound_picker_title)).setTextColor(style.getTextPrimaryColor()); + ((TextView) res.findViewById(R.id.settings_sound_picker_hint)).setTextColor(style.getTextSecondaryColor()); return res; } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (resultCode == Activity.RESULT_OK && requestCode == SOUND_PICKER_REQUEST_CODE) { + Uri ringtone = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI); + + if (ringtone != null) { + notificationPreferencesEditor.putString("globalSound", ringtone.toString()); + } else { + notificationPreferencesEditor.putString("globalSound", "none"); + } + notificationPreferencesEditor.commit(); + } + } } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/util/ViewUtils.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/util/ViewUtils.java index 575dd30070..b9ecaef873 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/util/ViewUtils.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/util/ViewUtils.java @@ -1,14 +1,9 @@ package im.actor.sdk.util; -import android.graphics.Color; -import android.os.Handler; import android.view.View; import android.view.animation.AlphaAnimation; import android.view.animation.Animation; -import android.view.animation.CycleInterpolator; import android.view.animation.ScaleAnimation; -import android.view.animation.Transformation; -import android.widget.AbsListView; import im.actor.sdk.view.MaterialInterpolator; @@ -40,6 +35,20 @@ public static void goneView(final View view, boolean isAnimated, boolean isSlow) } } + public static void goneViews(View... views) { + goneViews(true, views); + } + + public static void goneViews(boolean isAnimated, View... views) { + goneViews(isAnimated, true, views); + } + + public static void goneViews(boolean isAnimated, boolean isSlow, View... views) { + for (View view : views) { + goneView(view, isAnimated, isSlow); + } + } + public static void hideView(View view) { hideView(view, true); } @@ -67,6 +76,20 @@ public static void hideView(final View view, boolean isAnimated, boolean isSlow) } } + public static void hideViews(View... views) { + hideViews(true, views); + } + + public static void hideViews(boolean isAnimated, View... views) { + hideViews(isAnimated, true, views); + } + + public static void hideViews(boolean isAnimated, boolean isSlow, View... views) { + for (View view : views) { + hideView(view, isAnimated, isSlow); + } + } + public static void zoomOutView(final View view) { if (view == null) { return; @@ -128,6 +151,20 @@ public static void showView(final View view, boolean isAnimated, boolean isSlow) } } + public static void showViews(View... views) { + showViews(true, views); + } + + public static void showViews(boolean isAnimated, View... views) { + showViews(isAnimated, true, views); + } + + public static void showViews(boolean isAnimated, boolean isSlow, View... views) { + for (View view : views) { + showView(view, isAnimated, isSlow); + } + } + public static int blendColors(int color1, int color2, float amount, boolean inverse) { final byte ALPHA_CHANNEL = 24; final byte RED_CHANNEL = 16; diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fr_settings_notifications.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fr_settings_notifications.xml index 369628e52b..bb930e2a46 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fr_settings_notifications.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fr_settings_notifications.xml @@ -57,14 +57,13 @@ android:gravity="center_vertical" /> - - + + + + + + + + + + + + + + \ No newline at end of file diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_profile.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_profile.xml index 1a4dbfb975..f6fa6b17eb 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_profile.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_profile.xml @@ -184,6 +184,30 @@ android:layout_height="wrap_content" /> + + + + + + + Media Settings Notifications + Notifications sound Block user Unblock user Block {user}? @@ -483,8 +484,7 @@ Notifications Conversation tones Play sounds for incoming and outgoing messages. - Sound - Enable notification sounds + Vibration Enable vibration in notifications Group @@ -493,6 +493,10 @@ Enable group notifications only for mentions Show names and messages Show message text and sender name in notification panel + Sound + Enable notification sounds + Set sound + Set notification sound " messages in " " chats" " messages" From 1ae2d333d216138ea165a578edf7e9287f88d0ee Mon Sep 17 00:00:00 2001 From: Alashov Berkeli Date: Fri, 5 Aug 2016 11:01:07 +0500 Subject: [PATCH 239/414] refactor(android): notificationSettings: replaced divider views with DividerView and lambdas --- .../settings/NotificationsFragment.java | 119 +++++++----------- .../res/layout/fr_settings_notifications.xml | 31 ++--- 2 files changed, 59 insertions(+), 91 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/NotificationsFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/NotificationsFragment.java index 419ead7629..5511a2b136 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/NotificationsFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/NotificationsFragment.java @@ -33,22 +33,13 @@ public class NotificationsFragment extends BaseFragment { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View res = inflater.inflate(R.layout.fr_settings_notifications, container, false); res.setBackgroundColor(ActorSDK.sharedActor().style.getMainBackgroundColor()); - res.findViewById(R.id.divider).setBackgroundColor(ActorSDK.sharedActor().style.getDividerColor()); - res.findViewById(R.id.divider1).setBackgroundColor(ActorSDK.sharedActor().style.getDividerColor()); - res.findViewById(R.id.divider2).setBackgroundColor(ActorSDK.sharedActor().style.getDividerColor()); - res.findViewById(R.id.divider3).setBackgroundColor(ActorSDK.sharedActor().style.getDividerColor()); - res.findViewById(R.id.divider4).setBackgroundColor(ActorSDK.sharedActor().style.getDividerColor()); - res.findViewById(R.id.divider5).setBackgroundColor(ActorSDK.sharedActor().style.getDividerColor()); // Conversation tone final CheckBox enableTones = (CheckBox) res.findViewById(R.id.enableConversationTones); enableTones.setChecked(messenger().isConversationTonesEnabled()); - View.OnClickListener enableTonesListener = new View.OnClickListener() { - @Override - public void onClick(View v) { - messenger().changeConversationTonesEnabled(!messenger().isConversationTonesEnabled()); - enableTones.setChecked(messenger().isConversationTonesEnabled()); - } + View.OnClickListener enableTonesListener = v -> { + messenger().changeConversationTonesEnabled(!messenger().isConversationTonesEnabled()); + enableTones.setChecked(messenger().isConversationTonesEnabled()); }; ActorStyle style = ActorSDK.sharedActor().style; ((TextView) res.findViewById(R.id.settings_conversation_tones_title)).setTextColor(style.getTextPrimaryColor()); @@ -59,12 +50,9 @@ public void onClick(View v) { // Vibration final CheckBox enableVibration = (CheckBox) res.findViewById(R.id.enableVibration); enableVibration.setChecked(messenger().isNotificationVibrationEnabled()); - View.OnClickListener enableVibrationListener = new View.OnClickListener() { - @Override - public void onClick(View v) { - messenger().changeNotificationVibrationEnabled(!messenger().isNotificationVibrationEnabled()); - enableVibration.setChecked(messenger().isNotificationVibrationEnabled()); - } + View.OnClickListener enableVibrationListener = v -> { + messenger().changeNotificationVibrationEnabled(!messenger().isNotificationVibrationEnabled()); + enableVibration.setChecked(messenger().isNotificationVibrationEnabled()); }; enableVibration.setOnClickListener(enableVibrationListener); res.findViewById(R.id.vibrationCont).setOnClickListener(enableVibrationListener); @@ -74,12 +62,9 @@ public void onClick(View v) { // Group final CheckBox enableGroup = (CheckBox) res.findViewById(R.id.enableGroup); enableGroup.setChecked(messenger().isGroupNotificationsEnabled()); - View.OnClickListener enableGroupListener = new View.OnClickListener() { - @Override - public void onClick(View v) { - messenger().changeGroupNotificationsEnabled(!messenger().isGroupNotificationsEnabled()); - enableGroup.setChecked(messenger().isGroupNotificationsEnabled()); - } + View.OnClickListener enableGroupListener = v -> { + messenger().changeGroupNotificationsEnabled(!messenger().isGroupNotificationsEnabled()); + enableGroup.setChecked(messenger().isGroupNotificationsEnabled()); }; enableGroup.setOnClickListener(enableGroupListener); res.findViewById(R.id.groupCont).setOnClickListener(enableGroupListener); @@ -89,12 +74,9 @@ public void onClick(View v) { // Mentions final CheckBox enableGroupMentions = (CheckBox) res.findViewById(R.id.enableGroupMentions); enableGroupMentions.setChecked(messenger().isGroupNotificationsOnlyMentionsEnabled()); - View.OnClickListener enableGroupMentionsListener = new View.OnClickListener() { - @Override - public void onClick(View v) { - messenger().changeGroupNotificationsOnlyMentionsEnabled(!messenger().isGroupNotificationsOnlyMentionsEnabled()); - enableGroupMentions.setChecked(messenger().isGroupNotificationsOnlyMentionsEnabled()); - } + View.OnClickListener enableGroupMentionsListener = v -> { + messenger().changeGroupNotificationsOnlyMentionsEnabled(!messenger().isGroupNotificationsOnlyMentionsEnabled()); + enableGroupMentions.setChecked(messenger().isGroupNotificationsOnlyMentionsEnabled()); }; enableGroupMentions.setOnClickListener(enableGroupMentionsListener); res.findViewById(R.id.groupMentionsCont).setOnClickListener(enableGroupMentionsListener); @@ -104,12 +86,9 @@ public void onClick(View v) { // Names and messages final CheckBox enableText = (CheckBox) res.findViewById(R.id.enableTitles); enableText.setChecked(messenger().isShowNotificationsText()); - View.OnClickListener enableTextListener = new View.OnClickListener() { - @Override - public void onClick(View v) { - messenger().changeShowNotificationTextEnabled(!messenger().isShowNotificationsText()); - enableText.setChecked(messenger().isShowNotificationsText()); - } + View.OnClickListener enableTextListener = v -> { + messenger().changeShowNotificationTextEnabled(!messenger().isShowNotificationsText()); + enableText.setChecked(messenger().isShowNotificationsText()); }; enableText.setOnClickListener(enableTextListener); res.findViewById(R.id.titlesCont).setOnClickListener(enableTextListener); @@ -119,20 +98,17 @@ public void onClick(View v) { // Sound final CheckBox enableSound = (CheckBox) res.findViewById(R.id.enableSound); enableSound.setChecked(messenger().isNotificationSoundEnabled()); - View.OnClickListener enableSoundListener = new View.OnClickListener() { - @Override - public void onClick(View v) { - messenger().changeNotificationSoundEnabled(!messenger().isNotificationSoundEnabled()); - enableSound.setChecked(messenger().isNotificationSoundEnabled()); - - //show/hide sound picker - View cont = res.findViewById(R.id.soundPickerCont); - View div = res.findViewById(R.id.divider5); - if (messenger().isNotificationSoundEnabled()) { - ViewUtils.showViews(cont, div); - } else { - ViewUtils.goneViews(cont, div); - } + View.OnClickListener enableSoundListener = v -> { + messenger().changeNotificationSoundEnabled(!messenger().isNotificationSoundEnabled()); + enableSound.setChecked(messenger().isNotificationSoundEnabled()); + + //show/hide sound picker + View cont = res.findViewById(R.id.soundPickerCont); + View div = res.findViewById(R.id.divider); + if (messenger().isNotificationSoundEnabled()) { + ViewUtils.showViews(cont, div); + } else { + ViewUtils.goneViews(cont, div); } }; enableSound.setOnClickListener(enableSoundListener); @@ -143,32 +119,29 @@ public void onClick(View v) { // Sound picker notificationPreferences = getActivity().getSharedPreferences("notifications", Context.MODE_PRIVATE); notificationPreferencesEditor = notificationPreferences.edit(); - View.OnClickListener soundPickerListener = new View.OnClickListener() { - @Override - public void onClick(View v) { - Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER); - intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_NOTIFICATION); - intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true); - intent.putExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI, RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)); - - Uri currentSound = null; - String defaultPath = null; - Uri defaultUri = Settings.System.DEFAULT_NOTIFICATION_URI; - if (defaultUri != null) { - defaultPath = defaultUri.getPath(); - } + View.OnClickListener soundPickerListener = v -> { + Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER); + intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_NOTIFICATION); + intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true); + intent.putExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI, RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)); + + Uri currentSound = null; + String defaultPath = null; + Uri defaultUri = Settings.System.DEFAULT_NOTIFICATION_URI; + if (defaultUri != null) { + defaultPath = defaultUri.getPath(); + } - String path = notificationPreferences.getString("globalSound", defaultPath); - if (path != null && !path.equals("none")) { - if (path.equals(defaultPath)) { - currentSound = defaultUri; - } else { - currentSound = Uri.parse(path); - } + String path = notificationPreferences.getString("globalSound", defaultPath); + if (path != null && !path.equals("none")) { + if (path.equals(defaultPath)) { + currentSound = defaultUri; + } else { + currentSound = Uri.parse(path); } - intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, currentSound); - startActivityForResult(intent, SOUND_PICKER_REQUEST_CODE); } + intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, currentSound); + startActivityForResult(intent, SOUND_PICKER_REQUEST_CODE); }; res.findViewById(R.id.soundPickerCont).setOnClickListener(soundPickerListener); ((TextView) res.findViewById(R.id.settings_sound_picker_title)).setTextColor(style.getTextPrimaryColor()); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fr_settings_notifications.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fr_settings_notifications.xml index bb930e2a46..cf6a7aac6e 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fr_settings_notifications.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fr_settings_notifications.xml @@ -57,10 +57,9 @@ android:gravity="center_vertical" /> - + android:layout_height="@dimen/div_size"/> - + android:layout_height="@dimen/div_size"/> - + android:layout_height="@dimen/div_size"/> - + android:layout_height="@dimen/div_size"/> - + android:layout_height="@dimen/div_size"/> - + android:layout_height="@dimen/div_size"/> Date: Fri, 5 Aug 2016 11:18:12 +0500 Subject: [PATCH 240/414] fix(android): show/hide sound picker while initiliazing views in notificationsFragment --- .../settings/NotificationsFragment.java | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/NotificationsFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/NotificationsFragment.java index 5511a2b136..8acac69e3a 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/NotificationsFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/NotificationsFragment.java @@ -96,6 +96,15 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa ((TextView) res.findViewById(R.id.settings_titles_hint)).setTextColor(style.getTextSecondaryColor()); // Sound + View soundPickerCont = res.findViewById(R.id.soundPickerCont); + View soundPickerDivider = res.findViewById(R.id.divider); + + if (messenger().isNotificationSoundEnabled()) { + ViewUtils.showViews(false, soundPickerCont, soundPickerDivider); + } else { + ViewUtils.goneViews(false, soundPickerCont, soundPickerDivider); + } + final CheckBox enableSound = (CheckBox) res.findViewById(R.id.enableSound); enableSound.setChecked(messenger().isNotificationSoundEnabled()); View.OnClickListener enableSoundListener = v -> { @@ -103,12 +112,10 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa enableSound.setChecked(messenger().isNotificationSoundEnabled()); //show/hide sound picker - View cont = res.findViewById(R.id.soundPickerCont); - View div = res.findViewById(R.id.divider); if (messenger().isNotificationSoundEnabled()) { - ViewUtils.showViews(cont, div); + ViewUtils.showViews(soundPickerCont, soundPickerDivider); } else { - ViewUtils.goneViews(cont, div); + ViewUtils.goneViews(soundPickerCont, soundPickerDivider); } }; enableSound.setOnClickListener(enableSoundListener); From fc0fe934f4dbe0643b65900f22963f320dcb38e6 Mon Sep 17 00:00:00 2001 From: Alashov Berkeli Date: Fri, 5 Aug 2016 11:32:10 +0500 Subject: [PATCH 241/414] refactor(android): removed unused imports from rootActivity left from invite link handler --- .../sdk/controllers/root/RootActivity.java | 21 ------------------- 1 file changed, 21 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/root/RootActivity.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/root/RootActivity.java index b68f1cc510..1ef914f25c 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/root/RootActivity.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/root/RootActivity.java @@ -1,35 +1,15 @@ package im.actor.sdk.controllers.root; -import android.content.DialogInterface; import android.content.Intent; import android.graphics.drawable.ColorDrawable; import android.os.Bundle; import android.support.v4.app.Fragment; -import android.support.v7.app.AlertDialog; import android.support.v7.widget.Toolbar; -import android.widget.Toast; -import java.io.UnsupportedEncodingException; - -import im.actor.core.entity.Peer; -import im.actor.core.viewmodel.CommandCallback; -import im.actor.core.viewmodel.GroupVM; -import im.actor.runtime.HTTP; -import im.actor.runtime.android.AndroidContext; -import im.actor.runtime.function.Consumer; -import im.actor.runtime.http.HTTPResponse; -import im.actor.runtime.json.JSONException; -import im.actor.runtime.json.JSONObject; import im.actor.sdk.ActorSDK; import im.actor.sdk.R; -import im.actor.sdk.controllers.Intents; import im.actor.sdk.controllers.activity.BaseFragmentActivity; import im.actor.sdk.controllers.tools.InviteHandler; -import im.actor.sdk.intents.ActorIntent; -import im.actor.sdk.intents.ActorIntentActivity; - -import static im.actor.sdk.util.ActorSDKMessenger.groups; -import static im.actor.sdk.util.ActorSDKMessenger.messenger; /** * Root Activity of Application @@ -69,5 +49,4 @@ protected void onNewIntent(Intent intent) { super.onNewIntent(intent); InviteHandler.handleIntent(this, intent); } - } From dcb027d45dd4c0d00b29326d0955ef8e8924b3f9 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Fri, 5 Aug 2016 16:38:29 +0300 Subject: [PATCH 242/414] fix(android): close group after exit, copy invite link, bind group privacy status via VM --- .../android-sdk/src/main/AndroidManifest.xml | 2 +- .../src/main/java/im/actor/sdk/ActorSDK.java | 12 ++++++ .../controllers/group/GroupAdminFragment.java | 40 +++++++++++-------- .../controllers/group/GroupInfoFragment.java | 14 ++++++- .../main/res/layout/fragment_group_header.xml | 1 + .../src/main/res/values-ru/ui_text.xml | 2 +- 6 files changed, 52 insertions(+), 19 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/AndroidManifest.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/AndroidManifest.xml index aa775688df..fedd697df2 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/AndroidManifest.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/AndroidManifest.xml @@ -78,7 +78,7 @@ android:pathPrefix="/join/" android:scheme="https" /> diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorSDK.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorSDK.java index 28b11dbf9f..5e90a78673 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorSDK.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorSDK.java @@ -1068,4 +1068,16 @@ public interface OnDelegateViewHolder { T onNotDelegated(); } + + public static void returnToRoot(Context context) { + Intent i; + ActorIntent startIntent = ActorSDK.sharedActor().getDelegate().getStartIntent(); + if (startIntent != null && startIntent instanceof ActorIntentActivity) { + i = ((ActorIntentActivity) startIntent).getIntent(); + } else { + i = new Intent(context, RootActivity.class); + } + i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + context.startActivity(i); + } } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupAdminFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupAdminFragment.java index c37527e65f..1d80cf5ce0 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupAdminFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupAdminFragment.java @@ -11,12 +11,9 @@ import android.widget.Toast; import im.actor.core.entity.GroupType; -import im.actor.core.entity.Peer; -import im.actor.core.viewmodel.CommandCallback; import im.actor.core.viewmodel.GroupVM; -import im.actor.runtime.actors.messages.*; -import im.actor.runtime.actors.messages.Void; -import im.actor.runtime.mvvm.ValueDoubleListener; +import im.actor.runtime.mvvm.Value; +import im.actor.runtime.mvvm.ValueChangedListener; import im.actor.sdk.ActorSDK; import im.actor.sdk.R; import im.actor.sdk.controllers.BaseFragment; @@ -69,11 +66,18 @@ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, } else { groupTypeTitle.setText(R.string.group_type); } - if (groupVM.getShortName().get() == null) { - groupTypeValue.setText(R.string.group_type_private); - } else { - groupTypeValue.setText(R.string.group_type_pubic); - } + + bind(groupVM.getShortName(), new ValueChangedListener() { + @Override + public void onChanged(String val, Value valueModel) { + if (val == null) { + groupTypeValue.setText(R.string.group_type_private); + } else { + groupTypeValue.setText(R.string.group_type_pubic); + } + } + }); + if (groupVM.getIsCanEditAdministration().get()) { res.findViewById(R.id.groupTypeContainer).setOnClickListener(v -> { startActivity(new Intent(getContext(), GroupTypeActivity.class) @@ -156,13 +160,17 @@ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, .setNegativeButton(R.string.dialog_cancel, null) .setPositiveButton(R.string.alert_delete_group_yes, (d1, which1) -> { if (groupVM.getIsCanLeave().get()) { - execute(messenger().leaveAndDeleteGroup(groupVM.getId()), R.string.progress_common).failure(e -> { - Toast.makeText(getActivity(), R.string.toast_unable_leave, Toast.LENGTH_LONG).show(); - }); + execute(messenger().leaveAndDeleteGroup(groupVM.getId()), R.string.progress_common) + .then(aVoid -> ActorSDK.returnToRoot(getActivity())) + .failure(e -> { + Toast.makeText(getActivity(), R.string.toast_unable_leave, Toast.LENGTH_LONG).show(); + }); } else if (groupVM.getIsCanDelete().get()) { - execute(messenger().deleteGroup(groupVM.getId()), R.string.progress_common).failure(e -> { - Toast.makeText(getActivity(), R.string.toast_unable_delete_chat, Toast.LENGTH_LONG).show(); - }); + execute(messenger().deleteGroup(groupVM.getId()), R.string.progress_common) + .then(aVoid -> ActorSDK.returnToRoot(getActivity())) + .failure(e -> { + Toast.makeText(getActivity(), R.string.toast_unable_delete_chat, Toast.LENGTH_LONG).show(); + }); } }) .show(); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java index e46e26b9be..39fca58dfe 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java @@ -1,6 +1,9 @@ package im.actor.sdk.controllers.group; import android.app.AlertDialog; +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; import android.content.Intent; import android.graphics.Color; import android.graphics.drawable.ColorDrawable; @@ -190,6 +193,15 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa } shortNameCont.setVisibility(shortName != null ? View.VISIBLE : View.GONE); }); + final ClipboardManager clipboard = (ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE); + shortNameCont.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + String link = shortLinkView.getText().toString(); + clipboard.setPrimaryClip(ClipData.newPlainText(null, (link.contains("://") ? "" : "https://") + link)); + Toast.makeText(getActivity(), getString(R.string.invite_link_copied), Toast.LENGTH_SHORT).show(); + } + }); bind(groupVM.getAbout(), groupVM.getShortName(), (about, shortName) -> { descriptionContainer.setVisibility(about != null || shortName != null ? View.VISIBLE @@ -258,7 +270,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa .setMessage(getString(R.string.alert_leave_group_message).replace("%1$s", groupVM.getName().get())) .setPositiveButton(R.string.alert_leave_group_yes, (dialog2, which) -> { - execute(messenger().leaveAndDeleteGroup(chatId)); + execute(messenger().leaveAndDeleteGroup(chatId).then(aVoid -> ActorSDK.returnToRoot(getActivity()))); }) .setNegativeButton(R.string.dialog_cancel, null) .show() diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_group_header.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_group_header.xml index d8f203ba2d..00d92519b9 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_group_header.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_group_header.xml @@ -107,6 +107,7 @@ android:paddingBottom="8dp" android:paddingLeft="72dp" android:paddingRight="8dp" + android:background="@drawable/selector" android:paddingTop="8dp"> Поделиться историей Все сообщения будут доступны всем участникам Тип группы - Частная + Приватная Публичная From 28e8282bac3478875adaca45ee06b0beef25d12a Mon Sep 17 00:00:00 2001 From: kor_ka Date: Fri, 5 Aug 2016 17:14:13 +0300 Subject: [PATCH 243/414] fix(android): close edit group type if short name not changed, show error toast for empty short name --- .../im/actor/sdk/controllers/group/GroupTypeFragment.java | 7 ++++--- .../android-sdk/src/main/res/values/ui_text.xml | 3 +++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupTypeFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupTypeFragment.java index f9f7b0f01e..4b039bb25d 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupTypeFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupTypeFragment.java @@ -136,16 +136,17 @@ public boolean onOptionsItemSelected(MenuItem item) { if (isPublic) { String nShortName = publicShortName.getText().toString().trim(); if (nShortName.length() == 0) { - finishActivity(); + Toast.makeText(getActivity(), R.string.group_edit_change_short_name_error, Toast.LENGTH_SHORT).show(); return true; } if (nShortName.equals(groupVM.getShortName().get())) { + finishActivity(); return true; } execute(messenger().editGroupShortName(groupVM.getId(), nShortName).then(r -> { finishActivity(); }).failure(e -> { - Toast.makeText(getActivity(), "Unable to change group short name", Toast.LENGTH_SHORT).show(); + Toast.makeText(getActivity(), R.string.group_edit_change_short_name_error, Toast.LENGTH_SHORT).show(); })); } else { if (groupVM.getShortName().get() == null) { @@ -155,7 +156,7 @@ public boolean onOptionsItemSelected(MenuItem item) { execute(messenger().editGroupShortName(groupVM.getId(), null).then(r -> { finishActivity(); }).failure(e -> { - Toast.makeText(getActivity(), "Unable to change group short name", Toast.LENGTH_SHORT).show(); + Toast.makeText(getActivity(), R.string.group_edit_change_short_name_error, Toast.LENGTH_SHORT).show(); })); } } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml index 5b735412e0..d561c05217 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml @@ -397,6 +397,9 @@ bot + Unable to change group short name + + Your photo Photo From ec6eef175bb2322f265a79a8243ad4b69d02da6d Mon Sep 17 00:00:00 2001 From: rockjam Date: Fri, 5 Aug 2016 17:20:19 +0300 Subject: [PATCH 244/414] feat(server): join to public groups, join via link with short name --- .../im/actor/server/group/GroupUtils.scala | 2 +- .../server/group/http/GroupsHttpHandler.scala | 6 ++-- .../actor/server/api/http/json/models.scala | 2 +- .../service/groups/GroupsServiceImpl.scala | 36 ++++++++++--------- .../sequence/SequenceServiceImpl.scala | 11 +++++- 5 files changed, 36 insertions(+), 21 deletions(-) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupUtils.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupUtils.scala index 642b9e5935..4452337e0f 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupUtils.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupUtils.scala @@ -20,7 +20,7 @@ object GroupUtils { for { groups ← Future.sequence(groupIds map (GroupExtension(system).getApiStruct(_, clientUserId))) memberIds = getUserIds(groups) - users ← Future.sequence((userIds.toSet ++ memberIds.toSet).filterNot(_ == 0) map (UserUtils.safeGetUser(_, clientUserId, clientAuthId))) map (_.flatten) + users ← Future.sequence((userIds.toSet ++ memberIds).filterNot(_ == 0) map (UserUtils.safeGetUser(_, clientUserId, clientAuthId))) map (_.flatten) } yield (groups, users.toSeq) } } diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/http/GroupsHttpHandler.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/http/GroupsHttpHandler.scala index c369b9f72e..9579a21ddd 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/http/GroupsHttpHandler.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/http/GroupsHttpHandler.scala @@ -28,6 +28,7 @@ private[group] final class GroupsHttpHandler()(implicit system: ActorSystem) ext private val db = DbExtension(system).db private val fsAdapter = FileStorageExtension(system).fsAdapter private val globalNamesStorage = new GlobalNamesStorageKeyValueStorage + private val groupExt = GroupExtension(system) override def routes: Route = defaultVersion { @@ -61,7 +62,8 @@ private[group] final class GroupsHttpHandler()(implicit system: ActorSystem) ext result ← optInviteData map { case (groupId, optInviterId) ⇒ for { - groupInfo ← GroupExtension(system).getApiStruct(groupId, 0) + groupInfo ← groupExt.getApiStruct(groupId, 0) + isHistoryShared ← groupExt.isHistoryShared(groupId) groupTitle = groupInfo.title groupAvatar = groupInfo.avatar groupAvatarUrls ← avatarUrls(groupAvatar) @@ -76,7 +78,7 @@ private[group] final class GroupsHttpHandler()(implicit system: ActorSystem) ext } } yield Right( json.GroupInviteInfo( - group = json.GroupInfo(groupId, groupTitle, groupAvatarUrls), + group = json.GroupInfo(groupId, groupTitle, isHistoryShared, groupAvatarUrls), inviter = optInviterInfo ) ) diff --git a/actor-server/actor-http-api/src/main/scala/im/actor/server/api/http/json/models.scala b/actor-server/actor-http-api/src/main/scala/im/actor/server/api/http/json/models.scala index 5ea4b6d282..1741581134 100644 --- a/actor-server/actor-http-api/src/main/scala/im/actor/server/api/http/json/models.scala +++ b/actor-server/actor-http-api/src/main/scala/im/actor/server/api/http/json/models.scala @@ -5,7 +5,7 @@ case class Text(text: String) extends Content case class Image(imageUrl: String) extends Content case class Document(documentUrl: String) extends Content -case class GroupInfo(id: Int, title: String, avatars: Option[AvatarUrls]) +case class GroupInfo(id: Int, title: String, isHistoryShared: Boolean, avatars: Option[AvatarUrls]) case class InviterInfo(name: String, avatars: Option[AvatarUrls]) case class GroupInviteInfo(group: GroupInfo, inviter: Option[InviterInfo]) case class AvatarUrls(small: Option[String], large: Option[String], full: Option[String]) diff --git a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupsServiceImpl.scala b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupsServiceImpl.scala index e2de35d632..592ed3cca3 100644 --- a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupsServiceImpl.scala +++ b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupsServiceImpl.scala @@ -350,23 +350,27 @@ final class GroupsServiceImpl(groupInviteConfig: GroupInviteConfig)(implicit act override def doHandleGetGroupInviteUrl(groupPeer: ApiGroupOutPeer, clientData: ClientData): Future[HandlerResult[ResponseInviteUrl]] = authorized(clientData) { implicit client ⇒ - groupExt.getMemberIds(groupPeer.groupId) flatMap { - case (memberIds, _, _) ⇒ - if (!memberIds.contains(client.userId)) { - FastFuture.successful(Error(GroupRpcErrors.NotAMember)) - } else { - withGroupOutPeer(groupPeer) { - db.run(for { - token ← (GroupInviteTokenRepo.find(groupPeer.groupId, client.userId): @silent).headOption.flatMap { - case Some(invToken) ⇒ DBIO.successful(invToken.token) - case None ⇒ - val token = ACLUtils.accessToken(ThreadLocalSecureRandom.current()) - val inviteToken = GroupInviteToken(groupPeer.groupId, client.userId, token) - for (_ ← GroupInviteTokenRepo.create(inviteToken): @silent) yield token - } - } yield Ok(ResponseInviteUrl(genInviteUrl(token)))) - } + groupExt.getApiFullStruct(groupPeer.groupId, client.userId) flatMap { group ⇒ + val isMember = group.members.exists(_.userId == client.userId) + if (!isMember) { + FastFuture.successful(Error(GroupRpcErrors.NotAMember)) + } else { + withGroupOutPeer(groupPeer) { + for { + inviteString ← group.shortName match { + case Some(name) ⇒ FastFuture.successful(name) + case None ⇒ + db.run((GroupInviteTokenRepo.find(groupPeer.groupId, client.userId): @silent).headOption flatMap { + case Some(invToken) ⇒ DBIO.successful(invToken.token) + case None ⇒ + val token = ACLUtils.accessToken() + val inviteToken = GroupInviteToken(groupPeer.groupId, client.userId, token) + for (_ ← GroupInviteTokenRepo.create(inviteToken): @silent) yield token + }) + } + } yield Ok(ResponseInviteUrl(genInviteUrl(inviteString))) } + } } } diff --git a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/sequence/SequenceServiceImpl.scala b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/sequence/SequenceServiceImpl.scala index 478e5256cb..1fcd90b4d1 100644 --- a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/sequence/SequenceServiceImpl.scala +++ b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/sequence/SequenceServiceImpl.scala @@ -100,8 +100,17 @@ final class SequenceServiceImpl(config: SequenceServiceConfig)( ): Future[HandlerResult[ResponseGetReferencedEntitites]] = authorized(clientData) { client ⇒ (for { + // check access hash only for private groups. + // No need to check access hash for public groups. + privateGroups ← fromFuture((groups foldLeft FastFuture.successful(Vector.empty[ApiGroupOutPeer])) { + case (accFu, el) ⇒ + for { + acc ← accFu + isShared ← groupExt.isHistoryShared(el.groupId) + } yield if (isShared) acc else acc :+ el + }) _ ← fromFutureBoolean(CommonRpcErrors.InvalidAccessHash)(ACLUtils.checkUserOutPeers(users, client.authId)) - _ ← fromFutureBoolean(CommonRpcErrors.InvalidAccessHash)(ACLUtils.checkGroupOutPeers(groups)) + _ ← fromFutureBoolean(CommonRpcErrors.InvalidAccessHash)(ACLUtils.checkGroupOutPeers(privateGroups)) res ← fromFuture(GroupUtils.getGroupsUsers( groups map (_.groupId), users map (_.userId), client.userId, client.authId From adb625cae5324cf444210c51b4c7cb9c737f78e5 Mon Sep 17 00:00:00 2001 From: Actor Bot Date: Wed, 3 Aug 2016 22:04:21 +0300 Subject: [PATCH 245/414] chore(server): setting version to 3.0.0 --- actor-server/version.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actor-server/version.sbt b/actor-server/version.sbt index 5051b75100..867df91f76 100644 --- a/actor-server/version.sbt +++ b/actor-server/version.sbt @@ -1 +1 @@ -version in ThisBuild := "3.0.0-SNAPSHOT" +version in ThisBuild := "3.0.0" \ No newline at end of file From 8fcc1b86ce10a133773d3b95cc3eb3eab541fe5e Mon Sep 17 00:00:00 2001 From: Actor Bot Date: Wed, 3 Aug 2016 23:39:27 +0300 Subject: [PATCH 246/414] chore(server): setting version to 3.0.1-SNAPSHOT --- actor-server/version.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actor-server/version.sbt b/actor-server/version.sbt index 867df91f76..d46b3d6f4b 100644 --- a/actor-server/version.sbt +++ b/actor-server/version.sbt @@ -1 +1 @@ -version in ThisBuild := "3.0.0" \ No newline at end of file +version in ThisBuild := "3.0.1-SNAPSHOT" \ No newline at end of file From ae729850edd6fe1ea879db625ebde5d34ddd8a5e Mon Sep 17 00:00:00 2001 From: rockjam Date: Fri, 5 Aug 2016 19:06:45 +0300 Subject: [PATCH 247/414] fix(server): try to fix server hang on start --- .../scala/im/actor/server/group/http/GroupsHttpHandler.scala | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/http/GroupsHttpHandler.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/http/GroupsHttpHandler.scala index 9579a21ddd..de8fab6b34 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/http/GroupsHttpHandler.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/http/GroupsHttpHandler.scala @@ -28,7 +28,6 @@ private[group] final class GroupsHttpHandler()(implicit system: ActorSystem) ext private val db = DbExtension(system).db private val fsAdapter = FileStorageExtension(system).fsAdapter private val globalNamesStorage = new GlobalNamesStorageKeyValueStorage - private val groupExt = GroupExtension(system) override def routes: Route = defaultVersion { @@ -62,8 +61,8 @@ private[group] final class GroupsHttpHandler()(implicit system: ActorSystem) ext result ← optInviteData map { case (groupId, optInviterId) ⇒ for { - groupInfo ← groupExt.getApiStruct(groupId, 0) - isHistoryShared ← groupExt.isHistoryShared(groupId) + groupInfo ← GroupExtension(system).getApiStruct(groupId, 0) + isHistoryShared ← GroupExtension(system).isHistoryShared(groupId) groupTitle = groupInfo.title groupAvatar = groupInfo.avatar groupAvatarUrls ← avatarUrls(groupAvatar) From 9692f052e0e4278d64bf6630168752d860d81428 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Fri, 5 Aug 2016 19:56:16 +0300 Subject: [PATCH 248/414] chore(android): add AlertListBuilder --- .../controllers/group/GroupInfoFragment.java | 117 ++++++++---------- .../im/actor/sdk/util/AlertListBuilder.java | 65 ++++++++++ 2 files changed, 116 insertions(+), 66 deletions(-) create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/util/AlertListBuilder.java diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java index 39fca58dfe..31f8ddb7ac 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java @@ -44,6 +44,7 @@ import im.actor.sdk.controllers.BaseFragment; import im.actor.sdk.controllers.group.view.MembersAdapter; import im.actor.sdk.controllers.fragment.preview.ViewAvatarActivity; +import im.actor.sdk.util.AlertListBuilder; import im.actor.sdk.util.Screen; import im.actor.sdk.view.TintImageView; import im.actor.sdk.view.adapters.RecyclerListView; @@ -362,74 +363,58 @@ public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCoun public void onMemberClicked(UserVM userVM, boolean isInvitedByMe) { - CharSequence[] items; - if (groupVM.getIsCanKickAnyone().get() || (groupVM.getIsCanKickInvited().get() && isInvitedByMe)) { - items = new CharSequence[]{ - getString(R.string.group_context_message).replace("{0}", userVM.getName().get()), - getString(R.string.group_context_call).replace("{0}", userVM.getName().get()), - getString(R.string.group_context_view).replace("{0}", userVM.getName().get()), - getString(R.string.group_context_remove).replace("{0}", userVM.getName().get()), - }; - } else { - items = new CharSequence[]{ - getString(R.string.group_context_message).replace("{0}", userVM.getName().get()), - getString(R.string.group_context_call).replace("{0}", userVM.getName().get()), - getString(R.string.group_context_view).replace("{0}", userVM.getName().get()) - }; - } - - new AlertDialog.Builder(getActivity()) - .setItems(items, (dialog, which) -> { - if (which == 0) { - startActivity(Intents.openPrivateDialog(userVM.getId(), true, getActivity())); - } else if (which == 1) { - final ArrayList phones = userVM.getPhones().get(); - if (phones.size() == 0) { - Toast.makeText(getActivity(), "No phones available", Toast.LENGTH_SHORT).show(); - } else if (phones.size() == 1) { - startActivity(Intents.call(phones.get(0).getPhone())); - } else { - CharSequence[] sequences = new CharSequence[phones.size()]; - for (int i = 0; i < sequences.length; i++) { - try { - Phonenumber.PhoneNumber number = PhoneNumberUtil.getInstance().parse("+" + phones.get(i).getPhone(), "us"); - sequences[i] = phones.get(which).getTitle() + ": " + PhoneNumberUtil.getInstance().format(number, PhoneNumberUtil.PhoneNumberFormat.INTERNATIONAL); - } catch (NumberParseException e) { - e.printStackTrace(); - sequences[i] = phones.get(which).getTitle() + ": +" + phones.get(i).getPhone(); - } - } - new AlertDialog.Builder(getActivity()) - .setItems(sequences, (dialog1, which1) -> { - startActivity(Intents.call(phones.get(which1).getPhone())); - }) - .show() - .setCanceledOnTouchOutside(true); + AlertListBuilder alertListBuilder = new AlertListBuilder(); + final ArrayList phones = userVM.getPhones().get(); + alertListBuilder.addItem(getString(R.string.group_context_message).replace("{0}", userVM.getName().get()), () -> startActivity(Intents.openPrivateDialog(userVM.getId(), true, getActivity()))); + if (phones.size() != 0) { + alertListBuilder.addItem(getString(R.string.group_context_call).replace("{0}", userVM.getName().get()), () -> { + if (phones.size() == 1) { + startActivity(Intents.call(phones.get(0).getPhone())); + } else { + CharSequence[] sequences = new CharSequence[phones.size()]; + for (int i = 0; i < sequences.length; i++) { + try { + Phonenumber.PhoneNumber number = PhoneNumberUtil.getInstance().parse("+" + phones.get(i).getPhone(), "us"); + sequences[i] = phones.get(i).getTitle() + ": " + PhoneNumberUtil.getInstance().format(number, PhoneNumberUtil.PhoneNumberFormat.INTERNATIONAL); + } catch (NumberParseException e) { + e.printStackTrace(); + sequences[i] = phones.get(i).getTitle() + ": +" + phones.get(i).getPhone(); } - } else if (which == 2) { - ActorSDKLauncher.startProfileActivity(getActivity(), userVM.getId()); - } else if (which == 3) { - new AlertDialog.Builder(getActivity()) - .setMessage(getString(R.string.alert_group_remove_text).replace("{0}", userVM.getName().get())) - .setPositiveButton(R.string.alert_group_remove_yes, (dialog2, which1) -> { - execute(messenger().kickMember(chatId, userVM.getId()), - R.string.progress_common, new CommandCallback() { - @Override - public void onResult(Void res1) { - - } - - @Override - public void onError(Exception e) { - Toast.makeText(getActivity(), R.string.toast_unable_kick, Toast.LENGTH_SHORT).show(); - } - }); - }) - .setNegativeButton(R.string.dialog_cancel, null) - .show() - .setCanceledOnTouchOutside(true); } - }) + new AlertDialog.Builder(getActivity()) + .setItems(sequences, (dialog1, which1) -> { + startActivity(Intents.call(phones.get(which1).getPhone())); + }) + .show() + .setCanceledOnTouchOutside(true); + } + }); + } + alertListBuilder.addItem(getString(R.string.group_context_view).replace("{0}", userVM.getName().get()), () -> ActorSDKLauncher.startProfileActivity(getActivity(), userVM.getId())); + if (groupVM.getIsCanKickAnyone().get() || (groupVM.getIsCanKickInvited().get() && isInvitedByMe)) { + alertListBuilder.addItem(getString(R.string.group_context_remove).replace("{0}", userVM.getName().get()), () -> { + new AlertDialog.Builder(getActivity()) + .setMessage(getString(R.string.alert_group_remove_text).replace("{0}", userVM.getName().get())) + .setPositiveButton(R.string.alert_group_remove_yes, (dialog2, which1) -> { + execute(messenger().kickMember(chatId, userVM.getId()), + R.string.progress_common, new CommandCallback() { + @Override + public void onResult(Void res1) { + + } + + @Override + public void onError(Exception e) { + Toast.makeText(getActivity(), R.string.toast_unable_kick, Toast.LENGTH_SHORT).show(); + } + }); + }) + .setNegativeButton(R.string.dialog_cancel, null) + .show() + .setCanceledOnTouchOutside(true); + }); + } + alertListBuilder.build(getActivity()) .show() .setCanceledOnTouchOutside(true); } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/util/AlertListBuilder.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/util/AlertListBuilder.java new file mode 100644 index 0000000000..c90e7f6bc2 --- /dev/null +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/util/AlertListBuilder.java @@ -0,0 +1,65 @@ +package im.actor.sdk.util; + +import android.content.Context; + +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; + +public class AlertListBuilder { + + private ArrayList items = new ArrayList<>(); + + public AlertListBuilder addItem(String name, @NotNull SelectListener selectListener) { + items.add(new Item(name, selectListener)); + return this; + } + + private void select(int i) { + items.get(i).getOnClickListener().onSelected(); + } + + public CharSequence[] getItems() { + CharSequence[] res = new CharSequence[items.size()]; + for (int i = 0; i < items.size(); i++) { + res[i] = items.get(i).getName(); + } + return res; + } + + private class Item { + int id; + String name; + SelectListener selectListener; + + public Item(String name, @NotNull SelectListener selectListener) { + this.name = name; + this.selectListener = selectListener; + } + + public int getId() { + return id; + } + + public String getName() { + return name; + } + + @NotNull + public SelectListener getOnClickListener() { + return selectListener; + } + } + + public interface SelectListener { + void onSelected(); + } + + public android.app.AlertDialog.Builder build(Context context) { + android.app.AlertDialog.Builder builder = new android.app.AlertDialog.Builder(context); + builder.setItems(getItems(), (dialog, which) -> { + select(which); + }); + return builder; + } +} \ No newline at end of file From c5c9fff00c5353cdd70e87620cf169862c788ce9 Mon Sep 17 00:00:00 2001 From: rockjam Date: Fri, 5 Aug 2016 20:26:31 +0300 Subject: [PATCH 249/414] fix(server): isSharedHistory -> isPublic check in groups --- .../im/actor/server/group/http/GroupsHttpHandler.scala | 7 +++---- .../main/scala/im/actor/server/api/http/json/models.scala | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/http/GroupsHttpHandler.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/http/GroupsHttpHandler.scala index de8fab6b34..13d56db3fa 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/http/GroupsHttpHandler.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/http/GroupsHttpHandler.scala @@ -62,10 +62,9 @@ private[group] final class GroupsHttpHandler()(implicit system: ActorSystem) ext case (groupId, optInviterId) ⇒ for { groupInfo ← GroupExtension(system).getApiStruct(groupId, 0) - isHistoryShared ← GroupExtension(system).isHistoryShared(groupId) + isPublic ← GroupExtension(system).getApiFullStruct(groupId, 0) map (_.shortName.isDefined) groupTitle = groupInfo.title - groupAvatar = groupInfo.avatar - groupAvatarUrls ← avatarUrls(groupAvatar) + groupAvatarUrls ← avatarUrls(groupInfo.avatar) optInviterInfo ← optInviterId match { case Some(inviterId) ⇒ @@ -77,7 +76,7 @@ private[group] final class GroupsHttpHandler()(implicit system: ActorSystem) ext } } yield Right( json.GroupInviteInfo( - group = json.GroupInfo(groupId, groupTitle, isHistoryShared, groupAvatarUrls), + group = json.GroupInfo(groupId, groupTitle, isPublic, groupAvatarUrls), inviter = optInviterInfo ) ) diff --git a/actor-server/actor-http-api/src/main/scala/im/actor/server/api/http/json/models.scala b/actor-server/actor-http-api/src/main/scala/im/actor/server/api/http/json/models.scala index 1741581134..b3ae4a1c1c 100644 --- a/actor-server/actor-http-api/src/main/scala/im/actor/server/api/http/json/models.scala +++ b/actor-server/actor-http-api/src/main/scala/im/actor/server/api/http/json/models.scala @@ -5,7 +5,7 @@ case class Text(text: String) extends Content case class Image(imageUrl: String) extends Content case class Document(documentUrl: String) extends Content -case class GroupInfo(id: Int, title: String, isHistoryShared: Boolean, avatars: Option[AvatarUrls]) +case class GroupInfo(id: Int, title: String, isPublic: Boolean, avatars: Option[AvatarUrls]) case class InviterInfo(name: String, avatars: Option[AvatarUrls]) case class GroupInviteInfo(group: GroupInfo, inviter: Option[InviterInfo]) case class AvatarUrls(small: Option[String], large: Option[String], full: Option[String]) From 83369c63536635abdb8a8c7150dc742010d3da1f Mon Sep 17 00:00:00 2001 From: kor_ka Date: Fri, 5 Aug 2016 22:33:55 +0300 Subject: [PATCH 250/414] feat(android): add admin rights delegation --- .../controllers/group/GroupInfoFragment.java | 60 +--------- .../controllers/group/MembersFragment.java | 36 ++++++ .../group/view/MembersAdapter.java | 110 +++++++++++++++++- .../src/main/res/values-ru/ui_text.xml | 2 + .../src/main/res/values/ui_text.xml | 2 + .../main/java/im/actor/core/Messenger.java | 16 ++- .../main/java/im/actor/core/entity/Group.java | 3 +- .../core/modules/groups/GroupsModule.java | 9 ++ 8 files changed, 173 insertions(+), 65 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java index 31f8ddb7ac..1fe55a9705 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java @@ -311,7 +311,7 @@ public void onClick(View view) { if (groupMember.getUid() != myUid()) { UserVM userVM = users().get(groupMember.getUid()); if (userVM != null) { - onMemberClicked(userVM, groupMember.getInviterUid() == myUid()); + groupUserAdapter.onMemberClick(groupVM, userVM, groupMember.isAdministrator(), groupMember.getInviterUid() == myUid(), (BaseActivity) getActivity()); return true; } } @@ -361,64 +361,6 @@ public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCoun return res; } - public void onMemberClicked(UserVM userVM, boolean isInvitedByMe) { - - AlertListBuilder alertListBuilder = new AlertListBuilder(); - final ArrayList phones = userVM.getPhones().get(); - alertListBuilder.addItem(getString(R.string.group_context_message).replace("{0}", userVM.getName().get()), () -> startActivity(Intents.openPrivateDialog(userVM.getId(), true, getActivity()))); - if (phones.size() != 0) { - alertListBuilder.addItem(getString(R.string.group_context_call).replace("{0}", userVM.getName().get()), () -> { - if (phones.size() == 1) { - startActivity(Intents.call(phones.get(0).getPhone())); - } else { - CharSequence[] sequences = new CharSequence[phones.size()]; - for (int i = 0; i < sequences.length; i++) { - try { - Phonenumber.PhoneNumber number = PhoneNumberUtil.getInstance().parse("+" + phones.get(i).getPhone(), "us"); - sequences[i] = phones.get(i).getTitle() + ": " + PhoneNumberUtil.getInstance().format(number, PhoneNumberUtil.PhoneNumberFormat.INTERNATIONAL); - } catch (NumberParseException e) { - e.printStackTrace(); - sequences[i] = phones.get(i).getTitle() + ": +" + phones.get(i).getPhone(); - } - } - new AlertDialog.Builder(getActivity()) - .setItems(sequences, (dialog1, which1) -> { - startActivity(Intents.call(phones.get(which1).getPhone())); - }) - .show() - .setCanceledOnTouchOutside(true); - } - }); - } - alertListBuilder.addItem(getString(R.string.group_context_view).replace("{0}", userVM.getName().get()), () -> ActorSDKLauncher.startProfileActivity(getActivity(), userVM.getId())); - if (groupVM.getIsCanKickAnyone().get() || (groupVM.getIsCanKickInvited().get() && isInvitedByMe)) { - alertListBuilder.addItem(getString(R.string.group_context_remove).replace("{0}", userVM.getName().get()), () -> { - new AlertDialog.Builder(getActivity()) - .setMessage(getString(R.string.alert_group_remove_text).replace("{0}", userVM.getName().get())) - .setPositiveButton(R.string.alert_group_remove_yes, (dialog2, which1) -> { - execute(messenger().kickMember(chatId, userVM.getId()), - R.string.progress_common, new CommandCallback() { - @Override - public void onResult(Void res1) { - - } - - @Override - public void onError(Exception e) { - Toast.makeText(getActivity(), R.string.toast_unable_kick, Toast.LENGTH_SHORT).show(); - } - }); - }) - .setNegativeButton(R.string.dialog_cancel, null) - .show() - .setCanceledOnTouchOutside(true); - }); - } - alertListBuilder.build(getActivity()) - .show() - .setCanceledOnTouchOutside(true); - } - @Override public void onResume() { super.onResume(); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/MembersFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/MembersFragment.java index f2be8361c2..55260424f8 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/MembersFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/MembersFragment.java @@ -7,21 +7,27 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.AdapterView; import android.widget.LinearLayout; import android.widget.TextView; import fr.castorflex.android.circularprogressbar.CircularProgressBar; +import im.actor.core.entity.GroupMember; import im.actor.core.viewmodel.GroupVM; +import im.actor.core.viewmodel.UserVM; import im.actor.sdk.ActorSDK; import im.actor.sdk.R; import im.actor.sdk.controllers.BaseFragment; import im.actor.sdk.controllers.Intents; +import im.actor.sdk.controllers.activity.BaseActivity; import im.actor.sdk.controllers.group.view.MembersAdapter; import im.actor.sdk.util.Screen; import im.actor.sdk.view.DividerView; import im.actor.sdk.view.adapters.RecyclerListView; import static im.actor.sdk.util.ActorSDKMessenger.groups; +import static im.actor.sdk.util.ActorSDKMessenger.myUid; +import static im.actor.sdk.util.ActorSDKMessenger.users; public class MembersFragment extends BaseFragment { @@ -96,6 +102,36 @@ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, } list.setAdapter(adapter); + list.setOnItemClickListener((parent, view, position, id) -> { + Object item = parent.getItemAtPosition(position); + if (item != null && item instanceof GroupMember) { + GroupMember groupMember = (GroupMember) item; + if (groupMember.getUid() != myUid()) { + UserVM userVM = users().get(groupMember.getUid()); + if (userVM != null) { + startActivity(Intents.openPrivateDialog(userVM.getId(), true, getActivity())); + } + } + } + }); + + list.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() { + @Override + public boolean onItemLongClick(AdapterView adapterView, View view, int i, long l) { + Object item = adapterView.getItemAtPosition(i); + if (item != null && item instanceof GroupMember) { + GroupMember groupMember = (GroupMember) item; + if (groupMember.getUid() != myUid()) { + UserVM userVM = users().get(groupMember.getUid()); + if (userVM != null) { + adapter.onMemberClick(groupVM, userVM, groupMember.isAdministrator(), groupMember.getInviterUid() == myUid(), (BaseActivity) getActivity()); + return true; + } + } + } + return false; + } + }); progressView = (CircularProgressBar) res.findViewById(R.id.loadingProgress); progressView.setIndeterminate(true); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/view/MembersAdapter.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/view/MembersAdapter.java index e5e75b74c1..71765dcfc3 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/view/MembersAdapter.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/view/MembersAdapter.java @@ -1,19 +1,34 @@ package im.actor.sdk.controllers.group.view; import android.app.Activity; +import android.app.AlertDialog; import android.content.Context; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; +import android.widget.Toast; + +import com.google.i18n.phonenumbers.NumberParseException; +import com.google.i18n.phonenumbers.PhoneNumberUtil; +import com.google.i18n.phonenumbers.Phonenumber; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Optional; +import im.actor.core.viewmodel.CommandCallback; +import im.actor.core.viewmodel.GroupVM; +import im.actor.core.viewmodel.UserPhone; +import im.actor.runtime.actors.messages.*; +import im.actor.runtime.actors.messages.Void; import im.actor.sdk.ActorSDK; +import im.actor.sdk.ActorSDKLauncher; import im.actor.sdk.R; import im.actor.sdk.controllers.ActorBinder; +import im.actor.sdk.controllers.Intents; +import im.actor.sdk.controllers.activity.BaseActivity; +import im.actor.sdk.util.AlertListBuilder; import im.actor.sdk.util.Screen; import im.actor.sdk.view.avatar.AvatarView; import im.actor.sdk.view.adapters.HolderAdapter; @@ -56,8 +71,8 @@ public void setMembers(Collection members, boolean clear, boolean s if (b.isAdministrator() && !a.isAdministrator()) { return 1; } - String an = users().get(a.getInviterUid()).getName().get(); - String bn = users().get(b.getInviterUid()).getName().get(); + String an = users().get(a.getUid()).getName().get(); + String bn = users().get(b.getUid()).getName().get(); return an.compareTo(bn); }); this.members.addAll(Arrays.asList(membersArray)); @@ -155,11 +170,14 @@ public View init(GroupMember data, ViewGroup viewGroup, Context context) { @Override public void bind(GroupMember data, int position, Context context) { + boolean needRebind = user == null || data.getUid() != user.getId(); user = users().get(data.getUid()); ActorSDK.sharedActor().getMessenger().onUserVisible(data.getUid()); onlineBinding = BINDER.bindOnline(online, user); - avatarView.bind(user); + if (needRebind) { + avatarView.bind(user); + } userName.setText(user.getName().get()); @@ -187,4 +205,90 @@ public void dispose() { super.dispose(); BINDER.unbindAll(); } + + public void onMemberClick(GroupVM groupVM, UserVM userVM, boolean isAdministrator, boolean isInvitedByMe, BaseActivity activity) { + AlertListBuilder alertListBuilder = new AlertListBuilder(); + final ArrayList phones = userVM.getPhones().get(); + alertListBuilder.addItem(activity.getString(R.string.group_context_message).replace("{0}", userVM.getName().get()), () -> activity.startActivity(Intents.openPrivateDialog(userVM.getId(), true, activity))); + if (phones.size() != 0) { + alertListBuilder.addItem(activity.getString(R.string.group_context_call).replace("{0}", userVM.getName().get()), () -> { + if (phones.size() == 1) { + activity.startActivity(Intents.call(phones.get(0).getPhone())); + } else { + CharSequence[] sequences = new CharSequence[phones.size()]; + for (int i = 0; i < sequences.length; i++) { + try { + Phonenumber.PhoneNumber number = PhoneNumberUtil.getInstance().parse("+" + phones.get(i).getPhone(), "us"); + sequences[i] = phones.get(i).getTitle() + ": " + PhoneNumberUtil.getInstance().format(number, PhoneNumberUtil.PhoneNumberFormat.INTERNATIONAL); + } catch (NumberParseException e) { + e.printStackTrace(); + sequences[i] = phones.get(i).getTitle() + ": +" + phones.get(i).getPhone(); + } + } + new AlertDialog.Builder(activity) + .setItems(sequences, (dialog1, which1) -> { + activity.startActivity(Intents.call(phones.get(which1).getPhone())); + }) + .show() + .setCanceledOnTouchOutside(true); + } + }); + } + alertListBuilder.addItem(activity.getString(R.string.group_context_view).replace("{0}", userVM.getName().get()), () -> ActorSDKLauncher.startProfileActivity(activity, userVM.getId())); + if (groupVM.getIsCanKickAnyone().get() || (groupVM.getIsCanKickInvited().get() && isInvitedByMe)) { + alertListBuilder.addItem(activity.getString(R.string.group_context_remove).replace("{0}", userVM.getName().get()), () -> { + new AlertDialog.Builder(activity) + .setMessage(activity.getString(R.string.alert_group_remove_text).replace("{0}", userVM.getName().get())) + .setPositiveButton(R.string.alert_group_remove_yes, (dialog2, which1) -> { + activity.execute(messenger().kickMember(groupVM.getId(), userVM.getId()), + R.string.progress_common, new CommandCallback() { + @Override + public void onResult(Void res1) { + + } + + @Override + public void onError(Exception e) { + Toast.makeText(activity, R.string.toast_unable_kick, Toast.LENGTH_SHORT).show(); + } + }); + }) + .setNegativeButton(R.string.dialog_cancel, null) + .show() + .setCanceledOnTouchOutside(true); + }); + } + if (groupVM.getIsCanEditAdministration().get() && !userVM.isBot()) { + alertListBuilder.addItem(!isAdministrator ? activity.getResources().getString(R.string.group_make_admin) : activity.getResources().getString(R.string.group_revoke_admin), () -> { + if (!isAdministrator) { + messenger().makeAdmin(groupVM.getId(), userVM.getId()).start(new CommandCallback() { + @Override + public void onResult(Void res) { + + } + + @Override + public void onError(Exception e) { + + } + }); + } else { + messenger().revokeAdmin(groupVM.getId(), userVM.getId()).start(new CommandCallback() { + @Override + public void onResult(Void res) { + + } + + @Override + public void onError(Exception e) { + + } + }); + } + }); + } + alertListBuilder.build(activity) + .show() + .setCanceledOnTouchOutside(true); + } } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-ru/ui_text.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-ru/ui_text.xml index 20c0967f80..c120d8819c 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-ru/ui_text.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-ru/ui_text.xml @@ -373,6 +373,8 @@ Администрирование группы Администрирование + Назначить администратором + Аннулировать права администратора Все пользователи могут редактировать информацию о группе Удалить группу Вы потеряете все сообщения в этой группе diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml index d561c05217..6a7bb8bcbe 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml @@ -359,6 +359,8 @@ Leave group Leave channel Administration + Make admin + Revoke admin rights Group Administration Channel Administration diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java index 748047474c..871ad20f81 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java @@ -28,7 +28,6 @@ import im.actor.core.entity.Peer; import im.actor.core.entity.PeerSearchEntity; import im.actor.core.entity.PeerSearchType; -import im.actor.core.entity.PeerType; import im.actor.core.entity.SearchResult; import im.actor.core.entity.Sex; import im.actor.core.entity.User; @@ -1647,6 +1646,21 @@ public Command makeAdmin(final int gid, final int uid) { .failure(e -> callback.onError(e)); } + /** + * Revoke member admin rights of group + * + * @param gid group's id + * @param uid user's id + * @return Command for execution + */ + @NotNull + @ObjectiveCName("revokeAdminCommandWithGid:withUid:") + public Command revokeAdmin(final int gid, final int uid) { + return callback -> modules.getGroupsModule().revokeAdmin(gid, uid) + .then(v -> callback.onResult(v)) + .failure(e -> callback.onError(e)); + } + /** * Transfer ownership of group * diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Group.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Group.java index 29f7939272..eb619df85a 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Group.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Group.java @@ -469,12 +469,11 @@ public Group editMembers(List added, List removed, int count } } // Adding members - outer: for (ApiMember a : added) { for (ApiMember m : nMembers) { if (m.getUid() == a.getUid()) { nMembers.remove(m); - continue outer; + break; } } nMembers.add(a); diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsModule.java index 0b15da2b12..3f4e982237 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/groups/GroupsModule.java @@ -17,6 +17,7 @@ import im.actor.core.api.ApiUserOutPeer; import im.actor.core.api.rpc.RequestCreateGroup; import im.actor.core.api.rpc.RequestDeleteGroup; +import im.actor.core.api.rpc.RequestDismissUserAdmin; import im.actor.core.api.rpc.RequestEditGroupAbout; import im.actor.core.api.rpc.RequestEditGroupShortName; import im.actor.core.api.rpc.RequestEditGroupTitle; @@ -228,6 +229,14 @@ public Promise makeAdmin(final int gid, final int uid) { .flatMap(r -> updates().waitForUpdate(r.getSeq())); } + public Promise revokeAdmin(final int gid, final int uid) { + return Promises.tuple(getGroups().getValueAsync(gid), users().getValueAsync(uid)) + .flatMap(groupUserTuple2 -> api(new RequestDismissUserAdmin( + new ApiGroupOutPeer(gid, groupUserTuple2.getT1().getAccessHash()), + new ApiUserOutPeer(uid, groupUserTuple2.getT2().getAccessHash())))) + .flatMap(r -> updates().waitForUpdate(r.getSeq())); + } + public Promise transferOwnership(final int gid, final int uid) { return Promises.tuple(getGroups().getValueAsync(gid), users().getValueAsync(uid)) .flatMap(groupUserTuple2 -> api(new RequestTransferOwnership( From 9204adfe8d5b669da347e4ad51387dcc1a77d54f Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Sat, 6 Aug 2016 00:01:22 -0400 Subject: [PATCH 251/414] fix(iOS): Fixing speakers on video calls, remove video enable/disable --- .../Calls/AACallViewController.swift | 41 ++++++++++-------- .../Sources/Utils/AAAudioManager.swift | 42 +++++++++++++------ .../actor/core/modules/calls/CallActor.java | 6 ++- .../core/modules/calls/CallViewModels.java | 19 ++++++--- .../java/im/actor/core/viewmodel/CallVM.java | 9 +++- 5 files changed, 77 insertions(+), 40 deletions(-) diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Calls/AACallViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Calls/AACallViewController.swift index faa8cc7ea6..e3334dce96 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Calls/AACallViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Calls/AACallViewController.swift @@ -28,7 +28,7 @@ public class AACallViewController: AAViewController, RTCEAGLVideoViewDelegate { public let declineCallButtonText = UILabel() public let muteButton = AACircleButton(size: 72) - public let videoButton = AACircleButton(size: 72) + // public let videoButton = AACircleButton(size: 72) var isScheduledDispose = false var timer: NSTimer? @@ -89,13 +89,13 @@ public class AACallViewController: AAViewController, RTCEAGLVideoViewDelegate { Actor.toggleCallMuteWithCallId(self.callId) } - videoButton.image = UIImage.bundled("ic_video_44") - videoButton.title = AALocalized("CallsVideo") - videoButton.alpha = 0 + // videoButton.image = UIImage.bundled("ic_video_44") + // videoButton.title = AALocalized("CallsVideo") + // videoButton.alpha = 0 - videoButton.button.viewDidTap = { - Actor.toggleVideoEnabledWithCallId(self.callId) - } + // videoButton.button.viewDidTap = { + // Actor.toggleVideoEnabledWithCallId(self.callId) + // } // @@ -140,7 +140,7 @@ public class AACallViewController: AAViewController, RTCEAGLVideoViewDelegate { self.view.addSubview(declineCallButton) self.view.addSubview(declineCallButtonText) self.view.addSubview(muteButton) - self.view.addSubview(videoButton) + // self.view.addSubview(videoButton) self.view.addSubview(localView) } @@ -168,7 +168,12 @@ public class AACallViewController: AAViewController, RTCEAGLVideoViewDelegate { private func layoutButtons() { muteButton.frame = CGRectMake((self.view.width / 3 - 84) / 2, self.view.height - 72 - 49, 84, 72 + 5 + 44) - videoButton.frame = CGRectMake(2 * self.view.width / 3 + (self.view.width / 3 - 84) / 2, self.view.height - 72 - 49, 84, 72 + 5 + 44) +// videoButton.frame = CGRectMake(2 * self.view.width / 3 + (self.view.width / 3 - 84) / 2, self.view.height - 72 - 49, 84, 72 + 5 + 44) +// if call.isVideoPreferred.boolValue { +// videoButton.hidden = true +// } else { +// videoButton.hidden = false +// } if !declineCallButton.hidden || !answerCallButton.hidden { if !declineCallButton.hidden && !answerCallButton.hidden { @@ -256,7 +261,7 @@ public class AACallViewController: AAViewController, RTCEAGLVideoViewDelegate { if (self.call.isOutgoing) { self.muteButton.showViewAnimated() - self.videoButton.showViewAnimated() + // self.videoButton.showViewAnimated() self.answerCallButton.hidden = true self.answerCallButtonText.hidden = true @@ -277,7 +282,7 @@ public class AACallViewController: AAViewController, RTCEAGLVideoViewDelegate { } else if (ACCallState_Enum.CONNECTING == value.toNSEnum()) { self.muteButton.showViewAnimated() - self.videoButton.showViewAnimated() + // self.videoButton.showViewAnimated() self.answerCallButton.hidden = true self.answerCallButtonText.hidden = true @@ -291,7 +296,7 @@ public class AACallViewController: AAViewController, RTCEAGLVideoViewDelegate { } else if (ACCallState_Enum.IN_PROGRESS == value.toNSEnum()) { self.muteButton.showViewAnimated() - self.videoButton.showViewAnimated() + // self.videoButton.showViewAnimated() self.answerCallButton.hidden = true self.answerCallButtonText.hidden = true @@ -305,7 +310,7 @@ public class AACallViewController: AAViewController, RTCEAGLVideoViewDelegate { } else if (ACCallState_Enum.ENDED == value.toNSEnum()) { self.muteButton.hideViewAnimated() - self.videoButton.hideViewAnimated() + // self.videoButton.hideViewAnimated() self.answerCallButton.hidden = true self.answerCallButtonText.hidden = true @@ -359,9 +364,9 @@ public class AACallViewController: AAViewController, RTCEAGLVideoViewDelegate { if call.peer.isPrivate { // Bind Video Button - binder.bind(call.isVideoEnabled) { (value: JavaLangBoolean!) -> () in - self.videoButton.filled = value.booleanValue() - } +// binder.bind(call.isVideoEnabled) { (value: JavaLangBoolean!) -> () in +// self.videoButton.filled = value.booleanValue() +// } // Local Video can be only one, so we can just keep active track reference and handle changes binder.bind(call.ownVideoTracks, closure: { (videoTracks: ACArrayListMediaTrack!) in @@ -419,8 +424,8 @@ public class AACallViewController: AAViewController, RTCEAGLVideoViewDelegate { }) } else { - self.videoButton.filled = false - self.videoButton.enabled = false + // self.videoButton.filled = false + // self.videoButton.enabled = false } } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Utils/AAAudioManager.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Utils/AAAudioManager.swift index d8fdcdc42a..5af6d6543b 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Utils/AAAudioManager.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Utils/AAAudioManager.swift @@ -22,11 +22,11 @@ public class AAAudioManager: NSObject, AVAudioPlayerDelegate { private var isVisible = false private var isEnabled: Bool = false + private var isVideoPreferred = false private var openedConnections: Int = 0 public override init() { - super.init() - + super.init() } public func appVisible() { @@ -38,6 +38,8 @@ public class AAAudioManager: NSObject, AVAudioPlayerDelegate { } public func callStart(call: ACCallVM) { + isVideoPreferred = call.isVideoPreferred + if !call.isOutgoing { isRinging = true if isVisible { @@ -46,7 +48,7 @@ public class AAAudioManager: NSObject, AVAudioPlayerDelegate { audioRouter.category = AVAudioSessionCategoryPlayAndRecord audioRouter.mode = AVAudioSessionModeDefault audioRouter.currentRoute = .Speaker - audioRouter.isEnabled = isEnabled + audioRouter.isEnabled = true } ringtoneStart() } else { @@ -55,28 +57,42 @@ public class AAAudioManager: NSObject, AVAudioPlayerDelegate { vibrate() } else { isEnabled = true - audioRouter.category = AVAudioSessionCategoryPlayAndRecord - audioRouter.mode = AVAudioSessionModeVoiceChat - audioRouter.currentRoute = .Receiver - audioRouter.isEnabled = isEnabled + audioRouter.batchedUpdate { + audioRouter.category = AVAudioSessionCategoryPlayAndRecord + audioRouter.mode = AVAudioSessionModeVoiceChat + if isVideoPreferred { + audioRouter.currentRoute = .Speaker + } else { + audioRouter.currentRoute = .Receiver + } + audioRouter.isEnabled = true + } } } public func callAnswered(call: ACCallVM) { ringtoneEnd() isRinging = false - audioRouter.mode = AVAudioSessionModeVoiceChat - audioRouter.currentRoute = .Receiver + audioRouter.batchedUpdate { + audioRouter.mode = AVAudioSessionModeVoiceChat + if isVideoPreferred { + audioRouter.currentRoute = .Speaker + } else { + audioRouter.currentRoute = .Receiver + } + } } public func callEnd(call: ACCallVM) { ringtoneEnd() isRinging = false isEnabled = false - audioRouter.category = AVAudioSessionCategorySoloAmbient - audioRouter.mode = AVAudioSessionModeDefault - audioRouter.currentRoute = .Receiver - audioRouter.isEnabled = isEnabled + audioRouter.batchedUpdate { + audioRouter.category = AVAudioSessionCategorySoloAmbient + audioRouter.mode = AVAudioSessionModeDefault + audioRouter.currentRoute = .Receiver + audioRouter.isEnabled = false + } } public func peerConnectionStarted() { diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/calls/CallActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/calls/CallActor.java index cd4a9bb38d..329b300ce3 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/calls/CallActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/calls/CallActor.java @@ -66,7 +66,8 @@ public void preStart() { callBus.joinMasterBus(responseDoCall.getEventBusId(), responseDoCall.getDeviceId()); callBus.changeVideoEnabled(isVideoInitiallyEnabled); callBus.startOwn(); - callVM = callViewModels.spawnNewOutgoingVM(responseDoCall.getCallId(), peer, isVideoInitiallyEnabled); + callVM = callViewModels.spawnNewOutgoingVM(responseDoCall.getCallId(), peer, isVideoInitiallyEnabled, + isVideoInitiallyEnabled); }).failure(e -> self().send(PoisonPill.INSTANCE)); } else { api(new RequestGetCallInfo(callId)).then(responseGetCallInfo -> { @@ -76,7 +77,8 @@ public void preStart() { isVideoInitiallyEnabled = responseGetCallInfo.isVideoPreferred(); callBus.changeVideoEnabled(isVideoInitiallyEnabled); } - callVM = callViewModels.spawnNewIncomingVM(callId, peer, isVideoInitiallyEnabled, CallState.RINGING); + callVM = callViewModels.spawnNewIncomingVM(callId, peer, isVideoInitiallyEnabled, + isVideoInitiallyEnabled, CallState.RINGING); }).failure(e -> self().send(PoisonPill.INSTANCE)); } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/calls/CallViewModels.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/calls/CallViewModels.java index 094276249c..a42a3eba3b 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/calls/CallViewModels.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/calls/CallViewModels.java @@ -27,23 +27,29 @@ public synchronized CallVM getCall(long id) { return callModels.get(id); } - public synchronized CallVM spawnNewVM(long callId, Peer peer, boolean isOutgoing, boolean isVideoEnabled, ArrayList members, CallState callState) { - CallVM callVM = new CallVM(callId, peer, isOutgoing, isVideoEnabled, members, callState); + public synchronized CallVM spawnNewVM(long callId, Peer peer, boolean isOutgoing, + boolean isVideoEnabled, boolean isVideoPreffered, + ArrayList members, CallState callState) { + CallVM callVM = new CallVM(callId, peer, isOutgoing, isVideoEnabled, isVideoPreffered, + members, callState); synchronized (callModels) { callModels.put(callId, callVM); } return callVM; } - public synchronized CallVM spawnNewIncomingVM(long callId, Peer peer, boolean isVideoEnabled, CallState callState) { - CallVM callVM = new CallVM(callId, peer, false, isVideoEnabled, new ArrayList<>(), callState); + public synchronized CallVM spawnNewIncomingVM(long callId, Peer peer, boolean isVideoEnabled, + boolean isVideoPreffered, CallState callState) { + CallVM callVM = new CallVM(callId, peer, false, isVideoEnabled, isVideoPreffered, + new ArrayList<>(), callState); synchronized (callModels) { callModels.put(callId, callVM); } return callVM; } - public synchronized CallVM spawnNewOutgoingVM(long callId, Peer peer, boolean isVideoEnabled) { + public synchronized CallVM spawnNewOutgoingVM(long callId, Peer peer, boolean isVideoEnabled, + boolean isVideoPreferred) { ArrayList members = new ArrayList<>(); if (peer.getPeerType() == PeerType.PRIVATE || peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED) { @@ -56,6 +62,7 @@ public synchronized CallVM spawnNewOutgoingVM(long callId, Peer peer, boolean is } } } - return spawnNewVM(callId, peer, true, isVideoEnabled, members, CallState.RINGING); + return spawnNewVM(callId, peer, true, isVideoEnabled, isVideoPreferred, members, + CallState.RINGING); } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/CallVM.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/CallVM.java index be0a615179..3e5a55246e 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/CallVM.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/CallVM.java @@ -35,6 +35,8 @@ public class CallVM { private final BooleanValueModel isAudioEnabled; @Property("nonatomic, readonly") private final BooleanValueModel isVideoEnabled; + @Property("nonatomic, readonly") + private final boolean isVideoPreferred; @Property("nonatomic, readonly") private final ValueModel> members; @@ -45,7 +47,7 @@ public class CallVM { @Property("nonatomic, readonly") private final boolean isOutgoing; - public CallVM(long callId, Peer peer, boolean isOutgoing, boolean isVideoEnabled, ArrayList initialMembers, CallState state) { + public CallVM(long callId, Peer peer, boolean isOutgoing, boolean isVideoEnabled, boolean isVideoPreferred, ArrayList initialMembers, CallState state) { this.callId = callId; this.peer = peer; this.isOutgoing = isOutgoing; @@ -57,6 +59,7 @@ public CallVM(long callId, Peer peer, boolean isOutgoing, boolean isVideoEnabled this.members = new ValueModel<>("calls." + callId + ".members", new ArrayList<>(initialMembers)); this.isAudioEnabled = new BooleanValueModel("calls." + callId + ".audio_enabled", true); this.isVideoEnabled = new BooleanValueModel("calls." + callId + ".video_enabled", isVideoEnabled); + this.isVideoPreferred = isVideoPreferred; this.callStart = 0; } @@ -88,6 +91,10 @@ public ValueModel> getMembers() { return members; } + public boolean isVideoPreferred() { + return isVideoPreferred; + } + public void setCallStart(long callStart) { this.callStart = callStart; } From fab228c6da1742b2d01af1cacaae9fefb861f9ce Mon Sep 17 00:00:00 2001 From: Alashov Berkeli Date: Sat, 6 Aug 2016 13:30:19 +0500 Subject: [PATCH 252/414] refactor(android): lambdas in SecuritySettingsFragment DividerView instead of View --- .../settings/SecuritySettingsFragment.java | 101 ++++++------------ .../res/layout/fr_settings_encryption.xml | 3 +- 2 files changed, 35 insertions(+), 69 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/SecuritySettingsFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/SecuritySettingsFragment.java index 026209e8d5..e75f80b9f7 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/SecuritySettingsFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/SecuritySettingsFragment.java @@ -1,7 +1,6 @@ package im.actor.sdk.controllers.settings; import android.app.AlertDialog; -import android.content.DialogInterface; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; @@ -12,7 +11,6 @@ import java.util.ArrayList; import java.util.Collections; -import java.util.Comparator; import java.util.List; import im.actor.core.api.ApiAuthHolder; @@ -39,45 +37,29 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa loading = (TextView) res.findViewById(R.id.loading); loading.setTextColor(ActorSDK.sharedActor().style.getTextPrimaryColor()); loading.setVisibility(View.GONE); - loading.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - performLoad(); - } - }); + loading.setOnClickListener(v -> performLoad()); authItems = (LinearLayout) res.findViewById(R.id.authItems); - res.findViewById(R.id.divider).setBackgroundColor(ActorSDK.sharedActor().style.getDividerColor()); - res.findViewById(R.id.terminateSessions).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - new AlertDialog.Builder(getActivity()) - .setMessage(R.string.security_terminate_message) - .setPositiveButton(R.string.dialog_yes, new DialogInterface.OnClickListener() { + res.findViewById(R.id.terminateSessions).setOnClickListener(v -> new AlertDialog.Builder(getActivity()) + .setMessage(R.string.security_terminate_message) + .setPositiveButton(R.string.dialog_yes, (dialog, which) -> execute(messenger().terminateAllSessions(), R.string.progress_common, + new CommandCallback() { @Override - public void onClick(DialogInterface dialog, int which) { - execute(messenger().terminateAllSessions(), R.string.progress_common, - new CommandCallback() { - @Override - public void onResult(Void res) { - performLoad(); - } + public void onResult(Void res1) { + performLoad(); + } - @Override - public void onError(Exception e) { - performLoad(); - Toast.makeText(getActivity(), - R.string.security_toast_unable_remove_auth, Toast.LENGTH_SHORT) - .show(); - } - }); + @Override + public void onError(Exception e) { + performLoad(); + Toast.makeText(getActivity(), + R.string.security_toast_unable_remove_auth, Toast.LENGTH_SHORT) + .show(); } - }) - .setNegativeButton(R.string.dialog_no, null) - .show() - .setCanceledOnTouchOutside(true); - } - }); + })) + .setNegativeButton(R.string.dialog_no, null) + .show() + .setCanceledOnTouchOutside(true)); ((TextView) res.findViewById(R.id.settings_terminate_sessions_title)).setTextColor(ActorSDK.sharedActor().style.getTextPrimaryColor()); ((TextView) res.findViewById(R.id.settings_terminate_sessions_hint)).setTextColor(ActorSDK.sharedActor().style.getTextSecondaryColor()); performLoad(); @@ -96,12 +78,7 @@ public void onResult(List res) { goneView(loading, false); authItems.removeAllViews(); ArrayList items = new ArrayList(res); - Collections.sort(items, new Comparator() { - @Override - public int compare(ApiAuthSession lhs, ApiAuthSession rhs) { - return rhs.getAuthTime() - lhs.getAuthTime(); - } - }); + Collections.sort(items, (lhs, rhs) -> rhs.getAuthTime() - lhs.getAuthTime()); for (final ApiAuthSession item : items) { if (getActivity() == null) return; View view = getActivity().getLayoutInflater().inflate(R.layout.adapter_auth, authItems, false); @@ -115,34 +92,24 @@ public int compare(ApiAuthSession lhs, ApiAuthSession rhs) { ((TextView) view.findViewById(R.id.deviceTitle)).setText(deviceTitle); ((TextView) view.findViewById(R.id.deviceTitle)).setTextColor(ActorSDK.sharedActor().style.getTextPrimaryColor()); if (!isThisDevice) { - view.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - new AlertDialog.Builder(getActivity()) - .setMessage(getString(R.string.security_terminate_this_message).replace("{device}", item.getDeviceTitle() )) - .setPositiveButton(R.string.dialog_yes, new DialogInterface.OnClickListener() { + view.setOnClickListener(v -> new AlertDialog.Builder(getActivity()) + .setMessage(getString(R.string.security_terminate_this_message).replace("{device}", item.getDeviceTitle() )) + .setPositiveButton(R.string.dialog_yes, (dialog, which) -> execute(messenger().terminateSession(item.getId()), R.string.progress_common, + new CommandCallback() { @Override - public void onClick(DialogInterface dialog, int which) { - execute(messenger().terminateSession(item.getId()), R.string.progress_common, - new CommandCallback() { - @Override - public void onResult(Void res) { - performLoad(); - } + public void onResult(Void res1) { + performLoad(); + } - @Override - public void onError(Exception e) { - Toast.makeText(getActivity(), R.string.security_toast_unable_remove_auth , Toast.LENGTH_SHORT).show(); - performLoad(); - } - }); + @Override + public void onError(Exception e) { + Toast.makeText(getActivity(), R.string.security_toast_unable_remove_auth , Toast.LENGTH_SHORT).show(); + performLoad(); } - }) - .setNegativeButton(R.string.dialog_no, null) - .show() - .setCanceledOnTouchOutside(true); - } - }); + })) + .setNegativeButton(R.string.dialog_no, null) + .show() + .setCanceledOnTouchOutside(true)); } authItems.addView(view); } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fr_settings_encryption.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fr_settings_encryption.xml index 5bfb3cadca..f3188fbc03 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fr_settings_encryption.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fr_settings_encryption.xml @@ -14,8 +14,7 @@ android:layout_height="wrap_content" android:orientation="vertical"> - From 89654a7814de53678c9e21d0c5c8f1b04d69c050 Mon Sep 17 00:00:00 2001 From: Alashov Berkeli Date: Sat, 6 Aug 2016 14:39:39 +0500 Subject: [PATCH 253/414] feat(android): implemented setPrivacy/lastSeen inside SecuritySettings --- .../settings/SecuritySettingsFragment.java | 24 ++++++++++++-- .../res/layout/fr_settings_encryption.xml | 33 +++++++++++++++++++ .../src/main/res/values-ru/ui_text.xml | 11 +++++-- .../src/main/res/values/ui_text.xml | 11 +++++-- 4 files changed, 73 insertions(+), 6 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/SecuritySettingsFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/SecuritySettingsFragment.java index e75f80b9f7..e7be2501c0 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/SecuritySettingsFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/SecuritySettingsFragment.java @@ -10,6 +10,7 @@ import android.widget.Toast; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -40,6 +41,25 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa loading.setOnClickListener(v -> performLoad()); authItems = (LinearLayout) res.findViewById(R.id.authItems); + ((TextView) res.findViewById(R.id.settings_last_seen_title)).setTextColor(ActorSDK.sharedActor().style.getTextPrimaryColor()); + ((TextView) res.findViewById(R.id.settings_last_seen_hint)).setTextColor(ActorSDK.sharedActor().style.getTextSecondaryColor()); + + res.findViewById(R.id.lastSeen).setOnClickListener(v -> { + String[] itemsValues = new String[]{"always", "contacts", "none"}; + String[] items = new String[]{getString(R.string.security_last_seen_everybody), getString(R.string.security_last_seen_contacts), getString(R.string.security_last_seen_nobody)}; + int currentLastSeen = Arrays.asList(itemsValues).indexOf(messenger().getPrivacy()); + + new AlertDialog.Builder(getActivity()) + .setTitle(R.string.security_last_seen_title) + .setSingleChoiceItems(items, currentLastSeen, (dialog, which) -> { + messenger().setPrivacy(itemsValues[which]); + dialog.dismiss(); + }) + .setNegativeButton(R.string.dialog_cancel, null) + .setPositiveButton(R.string.dialog_ok, null) + .show(); + }); + res.findViewById(R.id.terminateSessions).setOnClickListener(v -> new AlertDialog.Builder(getActivity()) .setMessage(R.string.security_terminate_message) .setPositiveButton(R.string.dialog_yes, (dialog, which) -> execute(messenger().terminateAllSessions(), R.string.progress_common, @@ -93,7 +113,7 @@ public void onResult(List res) { ((TextView) view.findViewById(R.id.deviceTitle)).setTextColor(ActorSDK.sharedActor().style.getTextPrimaryColor()); if (!isThisDevice) { view.setOnClickListener(v -> new AlertDialog.Builder(getActivity()) - .setMessage(getString(R.string.security_terminate_this_message).replace("{device}", item.getDeviceTitle() )) + .setMessage(getString(R.string.security_terminate_this_message).replace("{device}", item.getDeviceTitle())) .setPositiveButton(R.string.dialog_yes, (dialog, which) -> execute(messenger().terminateSession(item.getId()), R.string.progress_common, new CommandCallback() { @Override @@ -103,7 +123,7 @@ public void onResult(Void res1) { @Override public void onError(Exception e) { - Toast.makeText(getActivity(), R.string.security_toast_unable_remove_auth , Toast.LENGTH_SHORT).show(); + Toast.makeText(getActivity(), R.string.security_toast_unable_remove_auth, Toast.LENGTH_SHORT).show(); performLoad(); } })) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fr_settings_encryption.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fr_settings_encryption.xml index f3188fbc03..b2925ebe74 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fr_settings_encryption.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fr_settings_encryption.xml @@ -19,6 +19,39 @@ android:layout_height="@dimen/div_size" android:visibility="gone" /> + + + + + + + + + + Оповещения Настройки чата Домашняя страница - Безопасность + Безопасность и Приватность Заблокированные пользователи Добавить ник Вы можете выбрать ник в {appName}. Так другие люди смогут найти вас без номера телефона.\n\nВы можете использовать латинские буквы, цифры и подчеркивание. Минимальная длина - 5 символов. @@ -535,13 +535,20 @@ - Безопасность + Безопасность и Приватность Уничтожить все сессии Произвести выход на всех устройствах Вы уверены, что хотите уничтожить сессии на всех устройствах? Все данные приложения на них будут удалены. Авторизованные устройства и сервисы Загрузка… + + Последняя активность + Укажите, кто может видеть вашу последнюю активность. + Все + Контакты + Никто + Заблокированные пользователи Вы никого не блокировали diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml index 5b735412e0..8b876fa2a0 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml @@ -183,7 +183,7 @@ Home page Notifications Chat Settings - Security + Security and Privacy Blocked list Set username Change username @@ -512,7 +512,7 @@ Is markdown enabled - Security + Security and Privacy Terminate all sessions Sign Out on all devices Are you sure you want to log out on all other devices? All app data will be lost on these devices. @@ -523,6 +523,13 @@ (This) Unable to load. Please try again. + + Last seen + Change who can see your Last Seen time. + Everybody + My Contacts + Nobody + Blocked list You haven\'t blocked anyone From fb8f199dde07779f55e14050844d637dcae6db79 Mon Sep 17 00:00:00 2001 From: Alashov Berkeli Date: Mon, 8 Aug 2016 17:29:45 +0500 Subject: [PATCH 254/414] fix(android): using core preferencesStorage instead of SharedPreferences for custom notificationSound paths --- .../java/im/actor/sdk/BaseActorSDKDelegate.java | 12 ++++-------- .../controllers/profile/ProfileFragment.java | 17 ++++++----------- .../settings/NotificationsFragment.java | 17 ++++++----------- 3 files changed, 16 insertions(+), 30 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/BaseActorSDKDelegate.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/BaseActorSDKDelegate.java index c761660bbe..a098b7b8a2 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/BaseActorSDKDelegate.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/BaseActorSDKDelegate.java @@ -1,7 +1,5 @@ package im.actor.sdk; -import android.content.Context; -import android.content.SharedPreferences; import android.net.Uri; import android.provider.Settings; import android.support.v4.app.Fragment; @@ -10,7 +8,6 @@ import org.jetbrains.annotations.Nullable; import im.actor.core.entity.Peer; -import im.actor.runtime.android.AndroidContext; import im.actor.runtime.android.view.BindedViewHolder; import im.actor.sdk.controllers.conversation.ChatFragment; import im.actor.sdk.controllers.conversation.attach.AbsAttachFragment; @@ -22,6 +19,8 @@ import im.actor.sdk.intents.ActorIntent; import im.actor.sdk.intents.ActorIntentFragmentActivity; +import static im.actor.sdk.util.ActorSDKMessenger.messenger; + /** * Base Implementation of Actor SDK Delegate. This class is recommended to subclass instead * of implementing ActorSDKDelegate @@ -125,9 +124,8 @@ public MessageHolder getCustomMessageViewHolder(int dataTypeHash, MessagesAdapte } public Uri getNotificationSoundForPeer(Peer peer) { - SharedPreferences sharedPreferences = AndroidContext.getContext().getSharedPreferences("notifications", Context.MODE_PRIVATE); - String globalSound = sharedPreferences.getString("userSound_" + peer.getPeerId(), null); + String globalSound = messenger().getPreferences().getString("userNotificationSound_" + peer.getPeerId()); if (globalSound != null) { if (globalSound.equals("none")) { return null; @@ -144,9 +142,7 @@ public int getNotificationColorForPeer(Peer peer) { } public Uri getNotificationSound() { - SharedPreferences sharedPreferences = AndroidContext.getContext().getSharedPreferences("notifications", Context.MODE_PRIVATE); - - String globalSound = sharedPreferences.getString("globalSound", null); + String globalSound = messenger().getPreferences().getString("globalNotificationSound"); if (globalSound != null) { if (globalSound.equals("none")) { return null; diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/profile/ProfileFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/profile/ProfileFragment.java index d44fcefb8c..b69fe6d2be 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/profile/ProfileFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/profile/ProfileFragment.java @@ -5,7 +5,6 @@ import android.content.ClipboardManager; import android.content.Context; import android.content.Intent; -import android.content.SharedPreferences; import android.graphics.Color; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; @@ -56,9 +55,6 @@ public class ProfileFragment extends BaseFragment { - private SharedPreferences notificationPreferences; - private SharedPreferences.Editor notificationPreferencesEditor; - public static int SOUND_PICKER_REQUEST_CODE = 122; public static final String EXTRA_UID = "uid"; @@ -442,9 +438,6 @@ public void onChanged(final String newUserName, Value valueModel) { DrawableCompat.setTint(drawable, style.getSettingsIconColor()); iconView.setImageDrawable(drawable); - notificationPreferences = getActivity().getSharedPreferences("notifications", Context.MODE_PRIVATE); - notificationPreferencesEditor = notificationPreferences.edit(); - ((TextView) notificationPickerContainer.findViewById(R.id.settings_notifications_picker_title)).setTextColor(style.getTextPrimaryColor()); notificationPickerContainer.setOnClickListener(view -> { Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER); @@ -459,7 +452,10 @@ public void onChanged(final String newUserName, Value valueModel) { defaultPath = defaultUri.getPath(); } - String path = notificationPreferences.getString("userSound_" + uid, defaultPath); + String path = messenger().getPreferences().getString("userNotificationSound_" + uid); + if (path == null) { + path = defaultPath; + } if (path != null && !path.equals("none")) { if (path.equals(defaultPath)) { currentSound = defaultUri; @@ -520,11 +516,10 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { Uri ringtone = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI); if (ringtone != null) { - notificationPreferencesEditor.putString("userSound_" + uid, ringtone.toString()); + messenger().getPreferences().putString("userNotificationSound_" + uid, ringtone.toString()); } else { - notificationPreferencesEditor.putString("userSound" + uid, "none"); + messenger().getPreferences().putString("userNotificationSound_" + uid, "none"); } - notificationPreferencesEditor.commit(); } } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/NotificationsFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/NotificationsFragment.java index 8acac69e3a..2e5dca1ff7 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/NotificationsFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/NotificationsFragment.java @@ -1,9 +1,7 @@ package im.actor.sdk.controllers.settings; import android.app.Activity; -import android.content.Context; import android.content.Intent; -import android.content.SharedPreferences; import android.media.RingtoneManager; import android.net.Uri; import android.os.Bundle; @@ -24,9 +22,6 @@ public class NotificationsFragment extends BaseFragment { - private SharedPreferences notificationPreferences; - private SharedPreferences.Editor notificationPreferencesEditor; - public static int SOUND_PICKER_REQUEST_CODE = 122; @Override @@ -124,8 +119,6 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa ((TextView) res.findViewById(R.id.settings_sound_hint)).setTextColor(style.getTextSecondaryColor()); // Sound picker - notificationPreferences = getActivity().getSharedPreferences("notifications", Context.MODE_PRIVATE); - notificationPreferencesEditor = notificationPreferences.edit(); View.OnClickListener soundPickerListener = v -> { Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER); intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_NOTIFICATION); @@ -139,7 +132,10 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa defaultPath = defaultUri.getPath(); } - String path = notificationPreferences.getString("globalSound", defaultPath); + String path = messenger().getPreferences().getString("globalNotificationSound"); + if (path == null) { + path = defaultPath; + } if (path != null && !path.equals("none")) { if (path.equals(defaultPath)) { currentSound = defaultUri; @@ -162,11 +158,10 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { Uri ringtone = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI); if (ringtone != null) { - notificationPreferencesEditor.putString("globalSound", ringtone.toString()); + messenger().getPreferences().putString("globalNotificationSound", ringtone.toString()); } else { - notificationPreferencesEditor.putString("globalSound", "none"); + messenger().getPreferences().putString("globalNotificationSound", "none"); } - notificationPreferencesEditor.commit(); } } } From bea8c2e1227a8c39fe9446a14236eec82bd65470 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Mon, 8 Aug 2016 16:13:20 +0300 Subject: [PATCH 255/414] design(android): async members list progress view --- .../sdk/controllers/auth/AuthActivity.java | 2 +- .../controllers/group/MembersFragment.java | 26 ++++++++++++++++++- .../group/view/MembersAdapter.java | 5 ++++ 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/auth/AuthActivity.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/auth/AuthActivity.java index 417b13b02c..6058d95483 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/auth/AuthActivity.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/auth/AuthActivity.java @@ -157,7 +157,7 @@ private void updateState(AuthState state, boolean force) { break; case LOGGED_IN: finish(); - ActorSDK.sharedActor().startMessagingApp(this); + ActorSDK.sharedActor().startAfterLoginActivity(this); break; } } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/MembersFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/MembersFragment.java index 55260424f8..38ba163161 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/MembersFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/MembersFragment.java @@ -8,6 +8,7 @@ import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; +import android.widget.FrameLayout; import android.widget.LinearLayout; import android.widget.TextView; @@ -32,6 +33,7 @@ public class MembersFragment extends BaseFragment { protected CircularProgressBar progressView; + private LinearLayout footer; public static MembersFragment create(int groupId) { MembersFragment res = new MembersFragment(); @@ -101,6 +103,18 @@ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, } } + footer = new LinearLayout(getActivity()); + list.addFooterView(footer); + CircularProgressBar botProgressView = new CircularProgressBar(getActivity()); + int padding = Screen.dp(16); + botProgressView.setPadding(padding, padding, padding, padding); + botProgressView.setIndeterminate(true); + FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(Screen.dp(72), Screen.dp(72)); + params.gravity = Gravity.CENTER; + FrameLayout cont = new FrameLayout(getActivity()); + cont.addView(botProgressView, params); + footer.addView(cont, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); + list.setAdapter(adapter); list.setOnItemClickListener((parent, view, position, id) -> { Object item = parent.getItemAtPosition(position); @@ -142,7 +156,17 @@ public boolean onItemLongClick(AdapterView adapterView, View view, int i, lon @Override public void onResume() { super.onResume(); - adapter.initLoad(() -> hideView(progressView)); + adapter.initLoad(new MembersAdapter.LoadedCallback() { + @Override + public void onLoaded() { + hideView(progressView); + } + + @Override + public void onLoadedToEnd() { + footer.setVisibility(View.INVISIBLE); + } + }); } @Override diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/view/MembersAdapter.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/view/MembersAdapter.java index 71765dcfc3..40276b37d4 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/view/MembersAdapter.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/view/MembersAdapter.java @@ -105,6 +105,8 @@ public void initLoad(LoadedCallback callback) { public interface LoadedCallback { void onLoaded(); + + void onLoadedToEnd(); } private void loadMore() { @@ -119,6 +121,9 @@ private void loadMore() { } nextMembers = groupMembersSlice.getNext(); loaddedToEnd = nextMembers == null; + if (loaddedToEnd && callback != null) { + callback.onLoadedToEnd(); + } loadInProgress = false; setMembers(groupMembersSlice.getMembers(), false, false); }); From fbaee97c308f9f4611b5c2c1ee3701f246d04688 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Mon, 8 Aug 2016 16:21:22 +0300 Subject: [PATCH 256/414] fix(android): custom app name for send media folder --- .../src/main/java/im/actor/core/AndroidMessenger.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/actor-sdk/sdk-core/core/core-android/src/main/java/im/actor/core/AndroidMessenger.java b/actor-sdk/sdk-core/core/core-android/src/main/java/im/actor/core/AndroidMessenger.java index e169abe598..2f0601248a 100644 --- a/actor-sdk/sdk-core/core/core-android/src/main/java/im/actor/core/AndroidMessenger.java +++ b/actor-sdk/sdk-core/core/core-android/src/main/java/im/actor/core/AndroidMessenger.java @@ -52,6 +52,7 @@ import im.actor.runtime.mvvm.Value; import im.actor.runtime.mvvm.ValueChangedListener; import im.actor.core.utils.GalleryScannerActor; +import im.actor.sdk.ActorSDK; import me.leolin.shortcutbadger.ShortcutBadger; import static im.actor.runtime.actors.ActorSystem.system; @@ -348,7 +349,9 @@ public Command sendUri(final Peer peer, final Uri uri) { } String externalPath = externalFile.getAbsolutePath(); - File dest = new File(externalPath + "/Actor/"); + File dest = new File(externalPath + "/" + + ActorSDK.sharedActor().getAppName() + + "/"); dest.mkdirs(); boolean isGif = picturePath != null && picturePath.endsWith(".gif"); From 1257789b9f4142815430738e4f88e38a3959b7cb Mon Sep 17 00:00:00 2001 From: kor_ka Date: Mon, 8 Aug 2016 16:41:12 +0300 Subject: [PATCH 257/414] fix(android): chat join text overlay --- .../actor/sdk/controllers/conversation/ChatFragment.java | 9 +++++++++ .../sdk/controllers/search/GlobalSearchBaseFragment.java | 2 -- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatFragment.java index 8bde15139e..0366519b63 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatFragment.java @@ -198,6 +198,13 @@ public void onResume() { inputOverlayText.setEnabled(true); showView(inputOverlayContainer, false); goneView(inputContainer, false); + } else if (groupVM.getIsCanJoin().get()) { + inputOverlayText.setText(getString(R.string.join)); + inputOverlayText.setTextColor(style.getListActionColor()); + inputOverlayText.setClickable(true); + inputOverlayText.setEnabled(true); + showView(inputOverlayContainer, false); + goneView(inputContainer, false); } else { inputOverlayText.setText(R.string.chat_not_member); inputOverlayText.setTextColor(style.getListActionColor()); @@ -230,6 +237,8 @@ public void onOverlayPressed() { messenger().changeNotificationsEnabled(peer, true); inputOverlayText.setText(getString(R.string.chat_mute)); } + } else if (groupVM.getIsCanJoin().get()) { + messenger().joinGroup(groupVM.getId()); } else { // TODO: Rejoin } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/search/GlobalSearchBaseFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/search/GlobalSearchBaseFragment.java index e0fa1ca495..febdcc1844 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/search/GlobalSearchBaseFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/search/GlobalSearchBaseFragment.java @@ -278,12 +278,10 @@ public void onClicked(SearchEntity item) { int peerId = item.getPeer().getPeerId(); switch (item.getPeer().getPeerType()) { case PRIVATE: - messenger().addContact(peerId); startActivity(Intents.openPrivateDialog(peerId, true, getActivity())); break; case GROUP: - messenger().joinGroup(peerId); startActivity(Intents.openGroupDialog(peerId, false, getActivity())); break; } From 78e762073dbfecae31fd0bb8c3bfd01b4509384c Mon Sep 17 00:00:00 2001 From: kor_ka Date: Mon, 8 Aug 2016 19:02:46 +0300 Subject: [PATCH 258/414] fix(android): view public group before join --- .../actor/sdk/controllers/tools/InviteHandler.java | 13 +++++++++---- .../src/main/java/im/actor/core/Messenger.java | 11 +++++++++++ .../im/actor/core/modules/search/SearchModule.java | 7 +++++++ 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/tools/InviteHandler.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/tools/InviteHandler.java index 243b7ff444..7d0434e29c 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/tools/InviteHandler.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/tools/InviteHandler.java @@ -46,13 +46,14 @@ public void apply(HTTPResponse httpResponse) { JSONObject data = new JSONObject(new String(httpResponse.getContent(), "UTF-8")); JSONObject group = data.getJSONObject("group"); String title = group.getString("title"); - if (group.has("id")) { + if (group.has("id") && group.has("isPublic")) { int gid = group.getInt("id"); + boolean isPublic = group.getBoolean("isPublic"); //Check if we have this group try { GroupVM groupVM = groups().get(gid); - if (groupVM.isMember().get()) { - //Have this group, is member, just open it + if (groupVM.isMember().get() || isPublic) { + //Have this group, is member or group is public, just open it activity.startActivity(Intents.openDialog(Peer.group(gid), false, activity)); } else { //Have this group, but not member, join it @@ -60,7 +61,11 @@ public void apply(HTTPResponse httpResponse) { } } catch (Exception e) { //Do not have this group, join it - joinViaToken(token, title, activity); + if (isPublic) { + messenger().findPublicGroupById(gid).then(peer -> activity.startActivity(Intents.openDialog(peer, false, activity))); + } else { + joinViaToken(token, title, activity); + } } } else { joinViaToken(token, title, activity); diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java index 871ad20f81..8755054bb5 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java @@ -1107,6 +1107,17 @@ public long loadLastMessageDate(Peer peer) { return getConversationVM(peer).getLastMessageDate(); } + /** + * Finding public by id + * + * @param gid group id + * @return found peer promise + */ + @ObjectiveCName("findPublicGroupByIdWithGid:") + public Promise findPublicGroupById(int gid) { + return modules.getSearchModule().findPublicGroupById(gid); + } + /** * Finding suitable mentions * diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/search/SearchModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/search/SearchModule.java index 5450bb2269..0fe4618dcd 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/search/SearchModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/search/SearchModule.java @@ -7,6 +7,7 @@ import java.util.ArrayList; import java.util.List; +import im.actor.core.api.ApiGroupOutPeer; import im.actor.core.api.ApiSearchAndCondition; import im.actor.core.api.ApiSearchCondition; import im.actor.core.api.ApiSearchContentType; @@ -137,6 +138,12 @@ public Promise> findPeers(ArrayList c .map(r -> new PeerSearchEntity(convert(r.getPeer()), r.getOptMatchString()))); } + public Promise findPublicGroupById(int gid) { + ArrayList groups = new ArrayList<>(); + groups.add(new ApiGroupOutPeer(gid, 0)); + return updates().loadRequiredPeers(new ArrayList<>(), groups).map(aVoid -> Peer.group(gid)); + } + public SearchValueModel buildSearchModel() { return new SearchValueModel<>(new GlobalSearchSource(context())); } From 793b08a6470b439892ce39ca9e5a4ea980a8b3b2 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Tue, 9 Aug 2016 15:18:19 +0300 Subject: [PATCH 259/414] fix(android): group -> channel in long tap menu --- .../dialogs/DialogsDefaultFragment.java | 25 ++++++++++++------- .../src/main/res/values-ru/ui_text.xml | 8 ++++++ .../src/main/res/values/ui_text.xml | 7 ++++++ 3 files changed, 31 insertions(+), 9 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/DialogsDefaultFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/DialogsDefaultFragment.java index 572471b46c..f885363dc2 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/DialogsDefaultFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/DialogsDefaultFragment.java @@ -5,6 +5,7 @@ import android.widget.Toast; import im.actor.core.entity.Dialog; +import im.actor.core.entity.GroupType; import im.actor.core.entity.PeerType; import im.actor.core.viewmodel.CommandCallback; import im.actor.core.viewmodel.GroupVM; @@ -70,12 +71,16 @@ public void onError(Exception e) { } else if (dialog.getPeer().getPeerType() == PeerType.GROUP) { GroupVM groupVM = groups().get(dialog.getPeer().getPeerId()); CharSequence[] items; + int dialogs_menu_view = groupVM.getGroupType().equals(GroupType.GROUP) ? R.string.dialogs_menu_group_view : R.string.dialogs_menu_channel_view; + int dialogs_menu_rename = groupVM.getGroupType().equals(GroupType.GROUP) ? R.string.dialogs_menu_group_rename : R.string.dialogs_menu_channel_rename; + int dialogs_menu_leave = groupVM.getGroupType().equals(GroupType.GROUP) ? R.string.dialogs_menu_group_leave : R.string.dialogs_menu_channel_leave; + int dialogs_menu_delete = groupVM.getGroupType().equals(GroupType.GROUP) ? R.string.dialogs_menu_group_delete : R.string.dialogs_menu_channel_delete; items = new CharSequence[]{ - getString(R.string.dialogs_menu_group_view), - getString(R.string.dialogs_menu_group_rename), - getString(groupVM.getIsCanLeave().get() ? R.string.dialogs_menu_group_leave : - groupVM.getIsCanDelete().get() ? R.string.dialogs_menu_group_delete : - R.string.dialogs_menu_group_delete), + getString(dialogs_menu_view), + getString(dialogs_menu_rename), + getString(groupVM.getIsCanLeave().get() ? dialogs_menu_leave : + groupVM.getIsCanDelete().get() ? dialogs_menu_delete : + dialogs_menu_delete), }; new AlertDialog.Builder(getActivity()) .setItems(items, (d, which) -> { @@ -84,12 +89,14 @@ public void onError(Exception e) { } else if (which == 1) { startActivity(Intents.editGroupTitle(dialog.getPeer().getPeerId(), getActivity())); } else if (which == 2) { + int alert_delete_title = groupVM.getGroupType().equals(GroupType.GROUP) ? R.string.alert_delete_group_title : R.string.alert_delete_channel_title; + int alert_leave_message = groupVM.getGroupType().equals(GroupType.GROUP) ? R.string.alert_leave_group_message : R.string.alert_leave_channel_message; new AlertDialog.Builder(getActivity()) - .setMessage(getString(groupVM.getIsCanLeave().get() ? R.string.alert_delete_group_title : - groupVM.getIsCanDelete().get() ? R.string.alert_delete_group_title : - R.string.alert_leave_group_message, dialog.getDialogTitle())) + .setMessage(getString(groupVM.getIsCanLeave().get() ? alert_delete_title : + groupVM.getIsCanDelete().get() ? alert_delete_title : + alert_leave_message, dialog.getDialogTitle())) .setNegativeButton(R.string.dialog_cancel, null) - .setPositiveButton(R.string.alert_leave_group_yes, (d1, which1) -> { + .setPositiveButton(groupVM.getIsCanLeave().get() ? R.string.alert_leave_group_yes : R.string.alert_delete_group_yes, (d1, which1) -> { if (groupVM.getIsCanLeave().get()) { execute(messenger().leaveAndDeleteGroup(dialog.getPeer().getPeerId()), R.string.progress_common).failure(e -> { Toast.makeText(getActivity(), R.string.toast_unable_leave, Toast.LENGTH_LONG).show(); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-ru/ui_text.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-ru/ui_text.xml index 4a230e8db8..a57692480d 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-ru/ui_text.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-ru/ui_text.xml @@ -229,6 +229,12 @@ Выйти из группы Удалить группу + Информация о канале + Переименовать канал + Выйти из канала + Удалить канал + + Контакты Еще нет контактов @@ -585,9 +591,11 @@ Вы уверены, что хотите выйти из группы "%1$s"? + Вы уверены, что хотите выйти из канала "%1$s"? Выйти Вы уверены, что хотите удалить группу "%1$s"? + Вы уверены, что хотите удалить канал "%1$s"? Удалить Вы уверены, что хотите удалить чат с "%1$s"? diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml index 663ad2f08d..ed7a708344 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml @@ -216,6 +216,11 @@ Exit group Delete group + View channel info + Rename channel + Exit channel + Delete channel + Contacts No contacts here yet @@ -585,9 +590,11 @@ Are you sure you want to exit group "%1$s"? + Are you sure you want to exit channel "%1$s"? Exit Delete "%1$s" group? + Delete "%1$s" channel? Delete Delete chat with "%1$s"? From 7320f36ebee5f704b8f41bd5859176159e46c210 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Tue, 9 Aug 2016 15:20:44 +0300 Subject: [PATCH 260/414] fix(android): drop group cache after delete --- .../im/actor/core/modules/messaging/router/RouterActor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java index 4deb34fec6..1338ef2ccd 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java @@ -557,7 +557,7 @@ private Promise onChatDelete(Peer peer) { updateChatState(peer); - return getDialogsRouter().onChatDelete(peer); + return getDialogsRouter().onChatDelete(peer).chain(aVoid -> onChatDropCache(peer)); } From 9404240ea2162582a0c101f009109ca4a5bce4f3 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Tue, 9 Aug 2016 20:16:07 +0300 Subject: [PATCH 261/414] fix(android): update string resources group -> channel --- .../controllers/dialogs/DialogsDefaultFragment.java | 12 ++++++------ .../sdk/controllers/group/GroupAdminFragment.java | 10 +++------- .../sdk/controllers/group/GroupInfoFragment.java | 2 ++ .../sdk/controllers/group/GroupTypeFragment.java | 8 +++++++- .../actor/sdk/controllers/group/MembersFragment.java | 7 +++++-- .../src/main/res/layout/fragment_members.xml | 1 + .../android-sdk/src/main/res/values-ru/ui_text.xml | 10 ++++++++++ .../android-sdk/src/main/res/values/ui_text.xml | 11 ++++++++++- 8 files changed, 44 insertions(+), 17 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/DialogsDefaultFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/DialogsDefaultFragment.java index f885363dc2..768fb33d7f 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/DialogsDefaultFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/DialogsDefaultFragment.java @@ -71,10 +71,10 @@ public void onError(Exception e) { } else if (dialog.getPeer().getPeerType() == PeerType.GROUP) { GroupVM groupVM = groups().get(dialog.getPeer().getPeerId()); CharSequence[] items; - int dialogs_menu_view = groupVM.getGroupType().equals(GroupType.GROUP) ? R.string.dialogs_menu_group_view : R.string.dialogs_menu_channel_view; - int dialogs_menu_rename = groupVM.getGroupType().equals(GroupType.GROUP) ? R.string.dialogs_menu_group_rename : R.string.dialogs_menu_channel_rename; - int dialogs_menu_leave = groupVM.getGroupType().equals(GroupType.GROUP) ? R.string.dialogs_menu_group_leave : R.string.dialogs_menu_channel_leave; - int dialogs_menu_delete = groupVM.getGroupType().equals(GroupType.GROUP) ? R.string.dialogs_menu_group_delete : R.string.dialogs_menu_channel_delete; + int dialogs_menu_view = groupVM.getGroupType() == GroupType.CHANNEL ? R.string.dialogs_menu_channel_view : R.string.dialogs_menu_group_view; + int dialogs_menu_rename = groupVM.getGroupType() == GroupType.CHANNEL ? R.string.dialogs_menu_channel_rename : R.string.dialogs_menu_group_rename; + int dialogs_menu_leave = groupVM.getGroupType() == GroupType.CHANNEL ? R.string.dialogs_menu_channel_leave : R.string.dialogs_menu_group_leave; + int dialogs_menu_delete = groupVM.getGroupType() == GroupType.CHANNEL ? R.string.dialogs_menu_channel_delete : R.string.dialogs_menu_group_delete; items = new CharSequence[]{ getString(dialogs_menu_view), getString(dialogs_menu_rename), @@ -89,8 +89,8 @@ public void onError(Exception e) { } else if (which == 1) { startActivity(Intents.editGroupTitle(dialog.getPeer().getPeerId(), getActivity())); } else if (which == 2) { - int alert_delete_title = groupVM.getGroupType().equals(GroupType.GROUP) ? R.string.alert_delete_group_title : R.string.alert_delete_channel_title; - int alert_leave_message = groupVM.getGroupType().equals(GroupType.GROUP) ? R.string.alert_leave_group_message : R.string.alert_leave_channel_message; + int alert_delete_title = groupVM.getGroupType() == GroupType.CHANNEL ? R.string.alert_delete_channel_title : R.string.alert_delete_group_title; + int alert_leave_message = groupVM.getGroupType() == GroupType.CHANNEL ? R.string.alert_leave_channel_message : R.string.alert_leave_group_message; new AlertDialog.Builder(getActivity()) .setMessage(getString(groupVM.getIsCanLeave().get() ? alert_delete_title : groupVM.getIsCanDelete().get() ? alert_delete_title : diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupAdminFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupAdminFragment.java index 1d80cf5ce0..d27dc58400 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupAdminFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupAdminFragment.java @@ -43,11 +43,7 @@ public GroupAdminFragment() { public void onCreate(Bundle saveInstance) { super.onCreate(saveInstance); groupVM = messenger().getGroup(getArguments().getInt("groupId")); - if (groupVM.getGroupType() == GroupType.CHANNEL) { - setTitle(R.string.channel_admin_title); - } else { - setTitle(R.string.group_admin_title); - } + setTitle(groupVM.getGroupType() == GroupType.CHANNEL ? R.string.channel_admin_title : R.string.group_admin_title); } @Nullable @@ -71,9 +67,9 @@ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Override public void onChanged(String val, Value valueModel) { if (val == null) { - groupTypeValue.setText(R.string.group_type_private); + groupTypeValue.setText(groupVM.getGroupType() == GroupType.CHANNEL ? R.string.channel_type_private : R.string.group_type_private); } else { - groupTypeValue.setText(R.string.group_type_pubic); + groupTypeValue.setText(groupVM.getGroupType() == GroupType.CHANNEL ? R.string.channel_type_pubic : R.string.group_type_pubic); } } }); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java index 1fe55a9705..2d25c54071 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java @@ -121,6 +121,8 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa TextView shortLinkView = (TextView) header.findViewById(R.id.shortNameLink); TextView addMember = (TextView) header.findViewById(R.id.addMemberAction); + addMember.setText(groupVM.getGroupType() == GroupType.CHANNEL ? R.string.channel_add_member : R.string.group_add_member); + TextView members = (TextView) header.findViewById(R.id.viewMembersAction); TextView leaveAction = (TextView) header.findViewById(R.id.leaveAction); TextView administrationAction = (TextView) header.findViewById(R.id.administrationAction); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupTypeFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupTypeFragment.java index 4b039bb25d..c500c3eea8 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupTypeFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupTypeFragment.java @@ -13,6 +13,7 @@ import android.widget.TextView; import android.widget.Toast; +import im.actor.core.entity.GroupType; import im.actor.core.viewmodel.GroupVM; import im.actor.sdk.ActorSDK; import im.actor.sdk.R; @@ -35,7 +36,6 @@ public static GroupTypeFragment create(int groupId) { private boolean isPublic; public GroupTypeFragment() { - setTitle(R.string.group_title); setRootFragment(true); setHomeAsUp(true); setShowHome(true); @@ -46,6 +46,8 @@ public void onCreate(Bundle saveInstance) { super.onCreate(saveInstance); groupVM = messenger().getGroup(getArguments().getInt("groupId")); + setTitle(groupVM.getGroupType() == GroupType.CHANNEL ? R.string.channel_title : R.string.group_title); + } @Nullable @@ -54,12 +56,16 @@ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, View res = inflater.inflate(R.layout.fragment_edit_type, container, false); res.setBackgroundColor(style.getBackyardBackgroundColor()); TextView publicTitle = (TextView) res.findViewById(R.id.publicTitle); + publicTitle.setText(groupVM.getGroupType() == GroupType.CHANNEL ? R.string.group_public_channel_title : R.string.group_public_group_title); publicTitle.setTextColor(style.getTextPrimaryColor()); TextView publicDescription = (TextView) res.findViewById(R.id.publicDescription); + publicDescription.setText(groupVM.getGroupType() == GroupType.CHANNEL ? R.string.group_public_channel_text : R.string.group_public_group_text); publicDescription.setTextColor(style.getTextSecondaryColor()); TextView privateTitle = (TextView) res.findViewById(R.id.privateTitle); + privateTitle.setText(groupVM.getGroupType() == GroupType.CHANNEL ? R.string.group_private_channel_title : R.string.group_private_group_title); privateTitle.setTextColor(style.getTextPrimaryColor()); TextView privateDescription = (TextView) res.findViewById(R.id.privateDescription); + privateDescription.setText(groupVM.getGroupType() == GroupType.CHANNEL ? R.string.group_private_channel_text : R.string.group_private_group_text); privateDescription.setTextColor(style.getTextSecondaryColor()); TextView publicLinkPrefix = (TextView) res.findViewById(R.id.publicLinkPrefix); publicLinkPrefix.setTextColor(style.getTextSecondaryColor()); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/MembersFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/MembersFragment.java index 38ba163161..4584c2b494 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/MembersFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/MembersFragment.java @@ -14,6 +14,7 @@ import fr.castorflex.android.circularprogressbar.CircularProgressBar; import im.actor.core.entity.GroupMember; +import im.actor.core.entity.GroupType; import im.actor.core.viewmodel.GroupVM; import im.actor.core.viewmodel.UserVM; import im.actor.sdk.ActorSDK; @@ -75,7 +76,7 @@ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, addMmemberTV.setTextSize(16); addMmemberTV.setPadding(Screen.dp(72), 0, 0, 0); addMmemberTV.setGravity(Gravity.CENTER_VERTICAL); - addMmemberTV.setText(R.string.group_add_member); + addMmemberTV.setText(groupVM.getGroupType() == GroupType.CHANNEL ? R.string.channel_add_member : R.string.group_add_member); addMmemberTV.setTextColor(ActorSDK.sharedActor().style.getTextPrimaryColor()); addMmemberTV.setOnClickListener(view -> { startActivity(new Intent(getActivity(), AddMemberActivity.class) @@ -104,6 +105,7 @@ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, } footer = new LinearLayout(getActivity()); + footer.setVisibility(View.INVISIBLE); list.addFooterView(footer); CircularProgressBar botProgressView = new CircularProgressBar(getActivity()); int padding = Screen.dp(16); @@ -160,11 +162,12 @@ public void onResume() { @Override public void onLoaded() { hideView(progressView); + showView(footer); } @Override public void onLoadedToEnd() { - footer.setVisibility(View.INVISIBLE); + hideView(footer); } }); } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_members.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_members.xml index 9ee96580ef..2d6d38c795 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_members.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_members.xml @@ -5,6 +5,7 @@ diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-ru/ui_text.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-ru/ui_text.xml index a57692480d..5149fa8b3d 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-ru/ui_text.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-ru/ui_text.xml @@ -342,6 +342,7 @@ Информация о группе + Информация о канале Участники {0} из {1} @@ -367,6 +368,7 @@ Документы Добавить в группу + Добавить в канал Покинуть группу Покинуть канал @@ -393,11 +395,19 @@ Приватная группа Публичные группы доступны для поиска, кто угодно можнет вступить в такую группу Публичная группа + + В приватый канал можно попасть только по личному приглашению + Приватый канал + Публичные каналы доступны для поиска, кто угодно можнет вступить в такой канал + Публичный канал + Поделиться историей Все сообщения будут доступны всем участникам Тип группы Приватная + Приватный Публичная + Публичный Администрирование канала diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml index ed7a708344..83ed6abe90 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml @@ -337,6 +337,7 @@ Group Info + Channel Info Members {0} of {1} @@ -362,6 +363,7 @@ Documents Add a member + Add a member Leave group Leave channel Administration @@ -378,7 +380,9 @@ Group Type Channel Type Public + Public Private + Private Delete Group You will lose all messages in this group. Delete Channel @@ -394,7 +398,12 @@ Public Group Public groups can be found in search, anyone can join them. Private Group - Private Group can only be joined via personal invitation. + Private group can only be joined via personal invitation. + + Public Channel + Public channels can be found in search, anyone can join them. + Private channel + Private channel can only be joined via personal invitation. You are not a member of this group From 6a6dabd25ae36d63ee8439fd5d036a2ea7fe8d6f Mon Sep 17 00:00:00 2001 From: rockjam Date: Mon, 8 Aug 2016 23:30:03 +0300 Subject: [PATCH 262/414] fix(server:group): forbid to grant rights to bots; code cleanup --- .../server/group/AdminCommandHandlers.scala | 7 +- .../server/group/GroupQueryHandlers.scala | 20 ++--- .../rpc/service/groups/GroupRpcErrors.scala | 1 + .../service/groups/GroupsServiceImpl.scala | 73 ++++++++++--------- .../service/messaging/HistoryHandlers.scala | 12 +-- 5 files changed, 57 insertions(+), 56 deletions(-) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/AdminCommandHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/AdminCommandHandlers.scala index 2e145fcc79..ba92089378 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/AdminCommandHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/AdminCommandHandlers.scala @@ -51,6 +51,7 @@ private[group] trait AdminCommandHandlers extends GroupsImplicits { } } + // TODO: duplicate isBot check protected def makeUserAdmin(cmd: MakeUserAdmin): Unit = { if (!state.permissions.canEditAdmins(cmd.clientUserId)) { sender() ! noPermission @@ -136,6 +137,7 @@ private[group] trait AdminCommandHandlers extends GroupsImplicits { } } + // TODO: duplicate isBot check protected def dismissUserAdmin(cmd: DismissUserAdmin): Unit = { if (!state.permissions.canEditAdmins(cmd.clientUserId)) { sender() ! noPermission @@ -223,6 +225,7 @@ private[group] trait AdminCommandHandlers extends GroupsImplicits { } } + // TODO: duplicate isBot check protected def transferOwnership(cmd: TransferOwnership): Unit = { if (!state.isOwner(cmd.clientUserId)) { sender() ! Status.Failure(CommonErrors.Forbidden) @@ -259,7 +262,7 @@ private[group] trait AdminCommandHandlers extends GroupsImplicits { protected def updateAdminSettings(cmd: UpdateAdminSettings): Unit = { if (!state.permissions.canEditAdminSettings(cmd.clientUserId)) { - sender() ! Status.Failure(NotAdmin) + sender() ! noPermission } else if (AdminSettings.fromBitMask(cmd.settingsBitMask) == state.adminSettings) { sender() ! UpdateAdminSettingsAck() } else { @@ -320,8 +323,6 @@ private[group] trait AdminCommandHandlers extends GroupsImplicits { persist(GroupDeleted(Instant.now, cmd.clientUserId)) { evt ⇒ commit(evt) - val dateMillis = evt.ts.toEpochMilli - val randomId = ACLUtils.randomLong() val ZeroPermissions = 0L val deleteGroupMembersUpdates: Vector[Update] = diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala index 3b3e769ccd..7742bb7bc7 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala @@ -185,9 +185,9 @@ trait GroupQueryHandlers { /** * Return group members, and number of members. - * If `clientUserId` is not a group member, return empty members list and 0 - * For `General` and `Public` groups return all members and their number. - * For `Channel` return members list only if `clientUserId` is group admin. Otherwise return empty members list and real members count + * If `clientUserId` is not a group member, return empty members list and 0 members count + * If group is group with async members - return list with single client user and real members count + * If group is regular group - return all group members and real members count */ private def membersAndCount(group: GroupState, clientUserId: Int): (Vector[ApiMember], Int) = { def apiMembers = group.members.toVector map { @@ -196,15 +196,11 @@ trait GroupQueryHandlers { } if (state.isMember(clientUserId)) { - state.groupType match { - case General ⇒ - apiMembers → group.membersCount - case Channel ⇒ - if (state.isAdmin(clientUserId)) - apiMembers → group.membersCount - else - apiMembers.find(_.userId == clientUserId).toVector → group.membersCount - case Unrecognized(v) ⇒ throw IncorrectGroupType(v) + if (state.isAsyncMembers) { + // compatibility with old clients + apiMembers.find(_.userId == clientUserId).toVector → group.membersCount + } else { + apiMembers → group.membersCount } } else { Vector.empty[ApiMember] → 0 diff --git a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupRpcErrors.scala b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupRpcErrors.scala index e668f74df1..74d3940dbe 100644 --- a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupRpcErrors.scala +++ b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupRpcErrors.scala @@ -24,5 +24,6 @@ object GroupRpcErrors { "Invalid group short name. Valid short name should contain from 5 to 32 characters, and may consist of latin characters, numbers and underscores", false, None) val ShortNameTaken = RpcError(400, "GROUP_SHORT_NAME_TAKEN", "This short name already belongs to other user or group, we are sorry!", false, None) val NoPermission = CommonRpcErrors.forbidden("You have no permission to execute this action") + val CantGrantToBot = RpcError(400, "CANT_GRANT_TO_BOT", "Can't grant this permissions to bot", false, None) } // format: ON diff --git a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupsServiceImpl.scala b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupsServiceImpl.scala index 592ed3cca3..3abe5e9100 100644 --- a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupsServiceImpl.scala +++ b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupsServiceImpl.scala @@ -41,6 +41,8 @@ final class GroupsServiceImpl(groupInviteConfig: GroupInviteConfig)(implicit act import IdUtils._ import ImageUtils._ + case object NoSeqStateDate extends RuntimeException("No SeqStateDate in response from group found") + override implicit val ec: ExecutionContext = actorSystem.dispatcher private val db: Database = DbExtension(actorSystem).db @@ -78,9 +80,11 @@ final class GroupsServiceImpl(groupInviteConfig: GroupInviteConfig)(implicit act authorized(clientData) { implicit client ⇒ withGroupOutPeer(groupPeer) { withUserOutPeer(userPeer) { - for { - (_, SeqStateDate(seq, state, date)) ← groupExt.makeUserAdmin(groupPeer.groupId, client.userId, client.authId, userPeer.userId) - } yield Ok(ResponseSeqDate(seq, state.toByteArray, date)) + (for { + _ ← fromFutureBoolean(GroupRpcErrors.CantGrantToBot)(userExt.getUser(userPeer.userId) map (!_.isBot)) + resp ← fromFuture(groupExt.makeUserAdmin(groupPeer.groupId, client.userId, client.authId, userPeer.userId)) + (_, SeqStateDate(seq, state, date)) = resp + } yield ResponseSeqDate(seq, state.toByteArray, date)).value } } } @@ -90,9 +94,10 @@ final class GroupsServiceImpl(groupInviteConfig: GroupInviteConfig)(implicit act authorized(clientData) { implicit client ⇒ withGroupOutPeer(groupPeer) { withUserOutPeer(userPeer) { - for { - SeqState(seq, state) ← groupExt.dismissUserAdmin(groupPeer.groupId, client.userId, client.authId, userPeer.userId) - } yield Ok(ResponseSeq(seq, state.toByteArray)) + (for { + _ ← fromFutureBoolean(GroupRpcErrors.CantGrantToBot)(userExt.getUser(userPeer.userId) map (!_.isBot)) + seqState ← fromFuture(groupExt.dismissUserAdmin(groupPeer.groupId, client.userId, client.authId, userPeer.userId)) + } yield ResponseSeq(seqState.seq, seqState.state.toByteArray)).value } } } @@ -147,21 +152,22 @@ final class GroupsServiceImpl(groupInviteConfig: GroupInviteConfig)(implicit act * @param groupPeer Group's peer * @param newOwner New group's owner */ - //TODO: figure out what date should be override protected def doHandleTransferOwnership(groupPeer: ApiGroupOutPeer, newOwner: ApiUserOutPeer, clientData: ClientData): Future[HandlerResult[ResponseSeqDate]] = authorized(clientData) { implicit client ⇒ withGroupOutPeer(groupPeer) { withUserOutPeer(newOwner) { - for { - SeqState(seq, state) ← groupExt.transferOwnership(groupPeer.groupId, client.userId, client.authId, newOwner.userId) - } yield Ok(ResponseSeqDate(seq, state.toByteArray, Instant.now.toEpochMilli)) + (for { + _ ← fromFutureBoolean(GroupRpcErrors.CantGrantToBot)(userExt.getUser(newOwner.userId) map (!_.isBot)) + seqState ← fromFuture(groupExt.transferOwnership(groupPeer.groupId, client.userId, client.authId, newOwner.userId)) + } yield ResponseSeqDate( + seq = seqState.seq, + state = seqState.state.toByteArray, + date = Instant.now.toEpochMilli + )).value } } } - case object NoSeqStateDate extends RuntimeException("No SeqStateDate in response from group found") - - // TODO: rewrite from DBIO to Future, and add access hash check override def doHandleEditGroupAvatar( groupPeer: ApiGroupOutPeer, randomId: Long, @@ -170,25 +176,26 @@ final class GroupsServiceImpl(groupInviteConfig: GroupInviteConfig)(implicit act clientData: ClientData ): Future[HandlerResult[ResponseEditGroupAvatar]] = authorized(clientData) { implicit client ⇒ - addOptimizations(optimizations) - val action = withFileLocation(fileLocation, AvatarSizeLimit) { - scaleAvatar(fileLocation.fileId) flatMap { - case Right(avatar) ⇒ - for { - UpdateAvatarAck(avatar, seqStateDate) ← DBIO.from(groupExt.updateAvatar(groupPeer.groupId, client.userId, client.authId, Some(avatar), randomId)) - SeqStateDate(seq, state, date) = seqStateDate.getOrElse(throw NoSeqStateDate) - } yield Ok(ResponseEditGroupAvatar( - avatar.get, - seq, - state.toByteArray, - date - )) - case Left(e) ⇒ - throw FileErrors.LocationInvalid + withGroupOutPeer(groupPeer) { + addOptimizations(optimizations) + val action = withFileLocation(fileLocation, AvatarSizeLimit) { + scaleAvatar(fileLocation.fileId) flatMap { + case Right(avatar) ⇒ + for { + UpdateAvatarAck(avatar, seqStateDate) ← DBIO.from(groupExt.updateAvatar(groupPeer.groupId, client.userId, client.authId, Some(avatar), randomId)) + SeqStateDate(seq, state, date) = seqStateDate.getOrElse(throw NoSeqStateDate) + } yield Ok(ResponseEditGroupAvatar( + avatar.get, + seq, + state.toByteArray, + date + )) + case Left(e) ⇒ + throw FileErrors.LocationInvalid + } } + db.run(action) } - - db.run(action) } override def doHandleRemoveGroupAvatar( @@ -455,9 +462,6 @@ final class GroupsServiceImpl(groupInviteConfig: GroupInviteConfig)(implicit act } } - /** - * all members of group can edit group topic - */ override def doHandleEditGroupTopic( groupPeer: ApiGroupOutPeer, randomId: Long, @@ -475,9 +479,6 @@ final class GroupsServiceImpl(groupInviteConfig: GroupInviteConfig)(implicit act } } - /** - * only admin can change group's about - */ override def doHandleEditGroupAbout( groupPeer: ApiGroupOutPeer, randomId: Long, diff --git a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/messaging/HistoryHandlers.scala b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/messaging/HistoryHandlers.scala index 7ad08bd90a..f1a718ff36 100644 --- a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/messaging/HistoryHandlers.scala +++ b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/messaging/HistoryHandlers.scala @@ -5,10 +5,12 @@ import java.time.Instant import akka.http.scaladsl.util.FastFuture import im.actor.api.rpc.PeerHelpers._ import im.actor.api.rpc._ +import im.actor.api.rpc.groups.ApiGroup import im.actor.api.rpc.messaging.{ ApiEmptyMessage, _ } import im.actor.api.rpc.misc.{ ResponseSeq, ResponseVoid } import im.actor.api.rpc.peers.{ ApiGroupOutPeer, ApiOutPeer, ApiPeerType, ApiUserOutPeer } import im.actor.api.rpc.sequence.ApiUpdateOptimization +import im.actor.api.rpc.users.ApiUser import im.actor.server.dialog.HistoryUtils import im.actor.server.group.{ CanSendMessageInfo, GroupUtils } import im.actor.server.model.Peer @@ -87,7 +89,7 @@ trait HistoryHandlers { authorized(clientData) { implicit client ⇒ for { (dialogs, nextOffset) ← dialogExt.fetchArchivedApiDialogs(client.userId, offset, limit) - (users, groups) ← db.run(getDialogsUsersGroups(dialogs.toSeq)) + (users, groups) ← getDialogsUsersGroups(dialogs.toSeq) } yield { val stripEntities = optimizations.contains(ApiUpdateOptimization.STRIP_ENTITIES) Ok(ResponseLoadArchived( @@ -110,7 +112,7 @@ trait HistoryHandlers { authorized(clientData) { implicit client ⇒ for { dialogs ← dialogExt.fetchApiDialogs(client.userId, Instant.ofEpochMilli(endDate), limit) - (users, groups) ← db.run(getDialogsUsersGroups(dialogs.toSeq)) + (users, groups) ← getDialogsUsersGroups(dialogs.toSeq) } yield { val stripEntities = optimizations.contains(ApiUpdateOptimization.STRIP_ENTITIES) @@ -314,7 +316,7 @@ trait HistoryHandlers { } yield (new DateTime(info.lastReceivedDate.toEpochMilli), new DateTime(info.lastReadDate.toEpochMilli))) } - private def getDialogsUsersGroups(dialogs: Seq[ApiDialog])(implicit client: AuthorizedClientData) = { + private def getDialogsUsersGroups(dialogs: Seq[ApiDialog])(implicit client: AuthorizedClientData): Future[(Set[ApiUser], Set[ApiGroup])] = { val (userIds, groupIds) = dialogs.foldLeft((Set.empty[Int], Set.empty[Int])) { case ((uacc, gacc), dialog) ⇒ if (dialog.peer.`type` == ApiPeerType.Private) { @@ -325,9 +327,9 @@ trait HistoryHandlers { } for { - groups ← DBIO.from(Future.sequence(groupIds map (groupExt.getApiStruct(_, client.userId)))) + groups ← Future.sequence(groupIds map (groupExt.getApiStruct(_, client.userId))) groupUserIds = groups.flatMap(g ⇒ g.members.flatMap(m ⇒ Seq(m.userId, m.inviterUserId)) :+ g.creatorUserId) - users ← DBIO.from(Future.sequence((userIds ++ groupUserIds).filterNot(_ == 0) map (UserUtils.safeGetUser(_, client.userId, client.authId)))) map (_.flatten) + users ← Future.sequence((userIds ++ groupUserIds).filterNot(_ == 0) map (UserUtils.safeGetUser(_, client.userId, client.authId))) map (_.flatten) } yield (users, groups) } From 7998e64fa47e72b3e21fb4adeca99b53d58ce1a8 Mon Sep 17 00:00:00 2001 From: rockjam Date: Tue, 9 Aug 2016 20:17:09 +0300 Subject: [PATCH 263/414] fix(server:group,rpc): reduce members load with GroupV2 optimization --- .../src/main/protobuf/groupV2.proto | 1 + .../actor/server/group/GroupOperations.scala | 4 +- .../actor/server/group/GroupProcessor.scala | 26 +-- .../server/group/GroupQueryHandlers.scala | 4 +- .../im/actor/server/group/GroupUtils.scala | 2 +- .../im/actor/api/rpc/EntitiesHelpers.scala | 149 ++++++++++++++++ .../service/groups/GroupsServiceImpl.scala | 30 +--- .../service/messaging/HistoryHandlers.scala | 168 ++++++------------ .../service/search/SearchServiceImpl.scala | 22 +-- 9 files changed, 244 insertions(+), 162 deletions(-) create mode 100644 actor-server/actor-rpc-api/src/main/scala/im/actor/api/rpc/EntitiesHelpers.scala diff --git a/actor-server/actor-core/src/main/protobuf/groupV2.proto b/actor-server/actor-core/src/main/protobuf/groupV2.proto index d402520650..47be829e9c 100644 --- a/actor-server/actor-core/src/main/protobuf/groupV2.proto +++ b/actor-server/actor-core/src/main/protobuf/groupV2.proto @@ -236,6 +236,7 @@ message GroupQueries { option (scalapb.message).extends = "GroupQuery"; int32 client_user_id = 1; + bool load_group_members = 2; } message GetApiStructResponse { diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupOperations.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupOperations.scala index 646e0cc15b..44b1b3f558 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupOperations.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupOperations.scala @@ -148,10 +148,10 @@ private[group] sealed trait Queries { GroupEnvelope(groupId) .withGetIntegrationToken(GetIntegrationToken(clientUserId = None))).mapTo[GetIntegrationTokenResponse] map (_.token) - def getApiStruct(groupId: Int, clientUserId: Int): Future[ApiGroup] = + def getApiStruct(groupId: Int, clientUserId: Int, loadGroupMembers: Boolean = true): Future[ApiGroup] = (viewRegion.ref ? GroupEnvelope(groupId) - .withGetApiStruct(GetApiStruct(clientUserId))).mapTo[GetApiStructResponse] map (_.struct) + .withGetApiStruct(GetApiStruct(clientUserId, loadGroupMembers))).mapTo[GetApiStructResponse] map (_.struct) def getApiFullStruct(groupId: Int, clientUserId: Int): Future[ApiGroupFull] = (viewRegion.ref ? diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala index 009961ccfa..4e68890fbf 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala @@ -169,20 +169,20 @@ private[group] final class GroupProcessor } protected def handleQuery: PartialFunction[Any, Future[Any]] = { - case _: GroupQuery if state.isNotCreated ⇒ FastFuture.failed(GroupNotFound(groupId)) + case _: GroupQuery if state.isNotCreated ⇒ FastFuture.failed(GroupNotFound(groupId)) // case _: GroupQuery if state.isDeleted ⇒ FastFuture.failed(GroupAlreadyDeleted(groupId)) // TODO: figure out how to propperly handle group deletion - case GetAccessHash() ⇒ getAccessHash - case GetTitle() ⇒ getTitle - case GetIntegrationToken(optClient) ⇒ getIntegrationToken(optClient) - case GetMembers() ⇒ getMembers - case LoadMembers(clientUserId, limit, offset) ⇒ loadMembers(clientUserId, limit, offset) - case IsChannel() ⇒ isChannel - case IsHistoryShared() ⇒ isHistoryShared - case GetApiStruct(clientUserId) ⇒ getApiStruct(clientUserId) - case GetApiFullStruct(clientUserId) ⇒ getApiFullStruct(clientUserId) - case CheckAccessHash(accessHash) ⇒ checkAccessHash(accessHash) - case CanSendMessage(clientUserId) ⇒ canSendMessage(clientUserId) - case LoadAdminSettings(clientUserId) ⇒ loadAdminSettings(clientUserId) + case GetAccessHash() ⇒ getAccessHash + case GetTitle() ⇒ getTitle + case GetIntegrationToken(optClient) ⇒ getIntegrationToken(optClient) + case GetMembers() ⇒ getMembers + case LoadMembers(clientUserId, limit, offset) ⇒ loadMembers(clientUserId, limit, offset) + case IsChannel() ⇒ isChannel + case IsHistoryShared() ⇒ isHistoryShared + case GetApiStruct(clientUserId, loadGroupMembers) ⇒ getApiStruct(clientUserId, loadGroupMembers) + case GetApiFullStruct(clientUserId) ⇒ getApiFullStruct(clientUserId) + case CheckAccessHash(accessHash) ⇒ checkAccessHash(accessHash) + case CanSendMessage(clientUserId) ⇒ canSendMessage(clientUserId) + case LoadAdminSettings(clientUserId) ⇒ loadAdminSettings(clientUserId) } override def afterCommit(e: Event) = { diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala index 7742bb7bc7..79c9396362 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala @@ -89,7 +89,7 @@ trait GroupQueryHandlers { //TODO: add ext! //TODO: what if state changes during request? - protected def getApiStruct(clientUserId: Int) = { + protected def getApiStruct(clientUserId: Int, loadGroupMembers: Boolean) = { val isMember = state.isMember(clientUserId) val (members, count) = membersAndCount(state, clientUserId) @@ -102,7 +102,7 @@ trait GroupQueryHandlers { avatar = state.avatar, isMember = Some(isMember), creatorUserId = state.creatorUserId, - members = members, + members = if (loadGroupMembers) members else Vector.empty, createDate = extractCreatedAtMillis(state), isAdmin = Some(state.isAdmin(clientUserId)), theme = state.topic, diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupUtils.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupUtils.scala index 4452337e0f..ab0ea666b6 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupUtils.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupUtils.scala @@ -12,7 +12,7 @@ object GroupUtils { def getUserIds(group: ApiGroup): Set[Int] = group.members.flatMap(m ⇒ Seq(m.userId, m.inviterUserId)).toSet + group.creatorUserId - def getUserIds(groups: Seq[ApiGroup]): Set[Int] = + private def getUserIds(groups: Seq[ApiGroup]): Set[Int] = groups.foldLeft(Set.empty[Int])(_ ++ getUserIds(_)) def getGroupsUsers(groupIds: Seq[Int], userIds: Seq[Int], clientUserId: Int, clientAuthId: Long)(implicit system: ActorSystem): Future[(Seq[ApiGroup], Seq[ApiUser])] = { diff --git a/actor-server/actor-rpc-api/src/main/scala/im/actor/api/rpc/EntitiesHelpers.scala b/actor-server/actor-rpc-api/src/main/scala/im/actor/api/rpc/EntitiesHelpers.scala new file mode 100644 index 0000000000..9fba44017c --- /dev/null +++ b/actor-server/actor-rpc-api/src/main/scala/im/actor/api/rpc/EntitiesHelpers.scala @@ -0,0 +1,149 @@ +package im.actor.api.rpc + +import akka.actor.ActorSystem +import im.actor.api.rpc.groups.ApiGroup +import im.actor.api.rpc.messaging._ +import im.actor.api.rpc.peers.{ ApiGroupOutPeer, ApiPeerType, ApiUserOutPeer } +import im.actor.api.rpc.users.ApiUser +import im.actor.server.dialog.HistoryUtils +import im.actor.server.group.GroupExtension +import im.actor.server.user.UserExtension + +import scala.concurrent.Future + +object EntitiesHelpers { + + private type UsersOrPeers = (Vector[ApiUser], Vector[ApiUserOutPeer]) + + private type GroupsOrPeers = (Vector[ApiGroup], Vector[ApiGroupOutPeer]) + + /** + * Load users and groups presented in `dialogs`. + * If `stripEntities = true`, return user and group peers instead of groups and users + * If `loadGroupMembers = true` members will be presented in `ApiGroup` object and loaded as users/user peers, + * otherwise exclude members from users/user peers and `ApiGroup` object. + */ + def usersAndGroupsByDialogs( + dialogs: Seq[ApiDialog], + stripEntities: Boolean, + loadGroupMembers: Boolean + )(implicit client: AuthorizedClientData, system: ActorSystem): Future[(UsersOrPeers, GroupsOrPeers)] = { + val (userIds, groupIds) = dialogs.foldLeft((Set.empty[Int], Set.empty[Int])) { + case ((uacc, gacc), dialog) ⇒ + dialog.peer.`type` match { + case ApiPeerType.Private | ApiPeerType.EncryptedPrivate ⇒ + (uacc ++ relatedUsers(dialog.message) ++ Set(dialog.peer.id, dialog.senderUserId), gacc) + case ApiPeerType.Group ⇒ + (uacc ++ relatedUsers(dialog.message) + dialog.senderUserId, gacc + dialog.peer.id) + } + } + usersAndGroupsByIds(groupIds, userIds, stripEntities, loadGroupMembers) + } + + def usersAndGroupsByShortDialogs( + dialogs: Seq[ApiDialogShort], + stripEntities: Boolean, + loadGroupMembers: Boolean + )(implicit client: AuthorizedClientData, system: ActorSystem): Future[(UsersOrPeers, GroupsOrPeers)] = { + val (userIds, groupIds) = dialogs.foldLeft((Set.empty[Int], Set.empty[Int])) { + case ((uids, gids), dialog) ⇒ + dialog.peer.`type` match { + case ApiPeerType.Group ⇒ (uids, gids + dialog.peer.id) + case ApiPeerType.Private | ApiPeerType.EncryptedPrivate ⇒ (uids + dialog.peer.id, gids) + } + } + usersAndGroupsByIds(groupIds, userIds, stripEntities, loadGroupMembers) + } + + def usersAndGroupsByIds( + groupIds: Set[Int], + userIds: Set[Int], + stripEntities: Boolean, + loadGroupMembers: Boolean + )(implicit client: AuthorizedClientData, system: ActorSystem): Future[(UsersOrPeers, GroupsOrPeers)] = { + import system.dispatcher + + for { + (groupsOrPeers, groupUserIds) ← groupsOrPeers(groupIds, stripEntities, loadGroupMembers) + usersOrPeers ← usersOrPeers((userIds ++ groupUserIds).toVector, stripEntities) + } yield ( + usersOrPeers, + groupsOrPeers + ) + } + + // get groups or group peers and ids of group members if needed + private def groupsOrPeers( + groupIds: Set[Int], + stripEntities: Boolean, + loadGroupMembers: Boolean + )(implicit client: AuthorizedClientData, system: ActorSystem): Future[(GroupsOrPeers, Set[Int])] = { + import system.dispatcher + + for { + groups ← Future.sequence(groupIds map (GroupExtension(system).getApiStruct(_, client.userId, loadGroupMembers))) + groupUserIds = if (loadGroupMembers) + groups.flatMap(g ⇒ g.members.flatMap(m ⇒ Seq(m.userId, m.inviterUserId)) :+ g.creatorUserId) + else + Set.empty[Int] + groupsOrPeers = if (stripEntities) { + Vector.empty[ApiGroup] → (groups map (g ⇒ ApiGroupOutPeer(g.id, g.accessHash))).toVector + } else { + groups.toVector → Vector.empty[ApiGroupOutPeer] + } + } yield ( + groupsOrPeers, + groupUserIds + ) + } + + // TODO: merge together with method in GroupServiceImpl + def usersOrPeers(userIds: Vector[Int], stripEntities: Boolean)(implicit client: AuthorizedClientData, system: ActorSystem): Future[UsersOrPeers] = { + import system.dispatcher + if (stripEntities) { + val users = Vector.empty[ApiUser] + val peers = Future.sequence(userIds filterNot (_ == HistoryUtils.SharedUserId) map { userId ⇒ + UserExtension(system).getAccessHash(userId, client.authId) map (hash ⇒ ApiUserOutPeer(userId, hash)) + }) + peers map (users → _) + } else { + val users = Future.sequence(userIds filterNot (_ == HistoryUtils.SharedUserId) map { userId ⇒ + UserExtension(system).getApiStruct(userId, client.userId, client.authId) + }) + val peers = Vector.empty[ApiUserOutPeer] + users map (_ → peers) + } + } + + def relatedUsers(message: ApiMessage): Set[Int] = { + message match { + case ApiServiceMessage(_, extOpt) ⇒ extOpt map relatedUsers getOrElse Set.empty + case ApiTextMessage(_, mentions, _) ⇒ mentions.toSet + case _: ApiJsonMessage ⇒ Set.empty + case _: ApiEmptyMessage ⇒ Set.empty + case _: ApiDocumentMessage ⇒ Set.empty + case _: ApiStickerMessage ⇒ Set.empty + case _: ApiUnsupportedMessage ⇒ Set.empty + case _: ApiBinaryMessage ⇒ Set.empty + case _: ApiEncryptedMessage ⇒ Set.empty + } + } + + private def relatedUsers(ext: ApiServiceEx): Set[Int] = + ext match { + case ApiServiceExContactRegistered(userId) ⇒ Set(userId) + case ApiServiceExChangedAvatar(_) ⇒ Set.empty + case ApiServiceExChangedTitle(_) ⇒ Set.empty + case ApiServiceExChangedTopic(_) ⇒ Set.empty + case ApiServiceExChangedAbout(_) ⇒ Set.empty + case ApiServiceExGroupCreated | _: ApiServiceExGroupCreated ⇒ Set.empty + case ApiServiceExPhoneCall(_) ⇒ Set.empty + case ApiServiceExPhoneMissed | _: ApiServiceExPhoneMissed ⇒ Set.empty + case ApiServiceExUserInvited(invitedUserId) ⇒ Set(invitedUserId) + case ApiServiceExUserJoined | _: ApiServiceExUserJoined ⇒ Set.empty + case ApiServiceExUserKicked(kickedUserId) ⇒ Set(kickedUserId) + case ApiServiceExUserLeft | _: ApiServiceExUserLeft ⇒ Set.empty + case _: ApiServiceExChatArchived | _: ApiServiceExChatRestored ⇒ Set.empty + } + +} diff --git a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupsServiceImpl.scala b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupsServiceImpl.scala index 3abe5e9100..d0c99160f7 100644 --- a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupsServiceImpl.scala +++ b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupsServiceImpl.scala @@ -35,6 +35,7 @@ import scala.concurrent.{ ExecutionContext, Future } final class GroupsServiceImpl(groupInviteConfig: GroupInviteConfig)(implicit actorSystem: ActorSystem) extends GroupsService { + import EntitiesHelpers._ import FileHelpers._ import FutureResultRpc._ import GroupCommands._ @@ -286,6 +287,7 @@ final class GroupsServiceImpl(groupInviteConfig: GroupInviteConfig)(implicit act addOptimizations(optimizations) withUserOutPeers(users) { val stripEntities = optimizations.contains(ApiUpdateOptimization.STRIP_ENTITIES) + val groupId = nextIntId() val typ = groupType map { case ApiGroupType.GROUP ⇒ GroupType.General @@ -305,13 +307,13 @@ final class GroupsServiceImpl(groupInviteConfig: GroupInviteConfig)(implicit act SeqStateDate(seq, state, date) = seqStateDate.getOrElse(throw NoSeqStateDate) group ← groupExt.getApiStruct(groupId, client.userId) memberIds = GroupUtils.getUserIds(group) - (apiUsers, apiPeers) ← usersOrPeers(memberIds.toVector, stripEntities) + (users, userPeers) ← usersOrPeers(memberIds.toVector, stripEntities) } yield Ok(ResponseCreateGroup( seq = seq, state = state.toByteArray, group = group, - users = apiUsers, - userPeers = apiPeers, + users = users, + userPeers = userPeers, date = date )) @@ -410,16 +412,17 @@ final class GroupsServiceImpl(groupInviteConfig: GroupInviteConfig)(implicit act invitingUserId = optInviter )) ((SeqStateDate(seq, state, date), userIds, randomId)) = joinResp - usersPeers ← fromFuture(usersOrPeers(userIds, stripEntities)) + up ← fromFuture(usersOrPeers(userIds, stripEntities)) + (users, userPeers) = up groupStruct ← fromFuture(groupExt.getApiStruct(groupId, client.userId)) } yield ResponseJoinGroup( groupStruct, seq, state.toByteArray, date, - usersPeers._1, + users, randomId, - usersPeers._2 + userPeers ) action.value @@ -523,21 +526,6 @@ final class GroupsServiceImpl(groupInviteConfig: GroupInviteConfig)(implicit act } } - private def usersOrPeers(userIds: Vector[Int], stripEntities: Boolean)(implicit client: AuthorizedClientData): Future[(Vector[ApiUser], Vector[ApiUserOutPeer])] = - if (stripEntities) { - val users = Vector.empty[ApiUser] - val peers = Future.sequence(userIds map { userId ⇒ - userExt.getAccessHash(userId, client.authId) map (hash ⇒ ApiUserOutPeer(userId, hash)) - }) - peers map (users → _) - } else { - val users = Future.sequence(userIds map { userId ⇒ - userExt.getApiStruct(userId, client.userId, client.authId) - }) - val peers = Vector.empty[ApiUserOutPeer] - users map (_ → peers) - } - private val inviteUriBase = s"${groupInviteConfig.baseUrl}/join/" private def genInviteUrl(token: String) = s"$inviteUriBase$token" diff --git a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/messaging/HistoryHandlers.scala b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/messaging/HistoryHandlers.scala index f1a718ff36..561f51e5be 100644 --- a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/messaging/HistoryHandlers.scala +++ b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/messaging/HistoryHandlers.scala @@ -5,19 +5,16 @@ import java.time.Instant import akka.http.scaladsl.util.FastFuture import im.actor.api.rpc.PeerHelpers._ import im.actor.api.rpc._ -import im.actor.api.rpc.groups.ApiGroup -import im.actor.api.rpc.messaging.{ ApiEmptyMessage, _ } +import im.actor.api.rpc.messaging._ import im.actor.api.rpc.misc.{ ResponseSeq, ResponseVoid } -import im.actor.api.rpc.peers.{ ApiGroupOutPeer, ApiOutPeer, ApiPeerType, ApiUserOutPeer } +import im.actor.api.rpc.peers.{ ApiOutPeer, ApiPeerType } import im.actor.api.rpc.sequence.ApiUpdateOptimization -import im.actor.api.rpc.users.ApiUser import im.actor.server.dialog.HistoryUtils -import im.actor.server.group.{ CanSendMessageInfo, GroupUtils } +import im.actor.server.group.CanSendMessageInfo import im.actor.server.model.Peer import im.actor.server.persist.contact.UserContactRepo import im.actor.server.persist.HistoryMessageRepo import im.actor.server.sequence.SeqState -import im.actor.server.user.UserUtils import org.joda.time.DateTime import slick.driver.PostgresDriver.api._ @@ -27,8 +24,8 @@ import scala.language.postfixOps trait HistoryHandlers { self: MessagingServiceImpl ⇒ - import DBIOResultRpc._ import HistoryUtils._ + import EntitiesHelpers._ import Implicits._ private val CantDelete = Error(CommonRpcErrors.forbidden("You can't delete these messages")) @@ -87,20 +84,20 @@ trait HistoryHandlers { clientData: ClientData ): Future[HandlerResult[ResponseLoadArchived]] = authorized(clientData) { implicit client ⇒ + val stripEntities = optimizations.contains(ApiUpdateOptimization.STRIP_ENTITIES) + val loadGroupMembers = !optimizations.contains(ApiUpdateOptimization.GROUPS_V2) + for { (dialogs, nextOffset) ← dialogExt.fetchArchivedApiDialogs(client.userId, offset, limit) - (users, groups) ← getDialogsUsersGroups(dialogs.toSeq) - } yield { - val stripEntities = optimizations.contains(ApiUpdateOptimization.STRIP_ENTITIES) - Ok(ResponseLoadArchived( - dialogs = dialogs.toVector, - nextOffset = nextOffset, - groups = if (stripEntities) Vector.empty else groups.toVector, - users = if (stripEntities) Vector.empty else users.toVector, - userPeers = users.toVector map (u ⇒ ApiUserOutPeer(u.id, u.accessHash)), - groupPeers = groups.toVector map (g ⇒ ApiGroupOutPeer(g.id, g.accessHash)) - )) - } + ((users, userPeers), (groups, groupPeers)) ← usersAndGroupsByDialogs(dialogs.toSeq, stripEntities, loadGroupMembers) + } yield Ok(ResponseLoadArchived( + dialogs = dialogs.toVector, + nextOffset = nextOffset, + groups = groups, + users = users, + userPeers = userPeers, + groupPeers = groupPeers + )) } override def doHandleLoadDialogs( @@ -110,20 +107,19 @@ trait HistoryHandlers { clientData: ClientData ): Future[HandlerResult[ResponseLoadDialogs]] = authorized(clientData) { implicit client ⇒ + val stripEntities = optimizations.contains(ApiUpdateOptimization.STRIP_ENTITIES) + val loadGroupMembers = !optimizations.contains(ApiUpdateOptimization.GROUPS_V2) + for { dialogs ← dialogExt.fetchApiDialogs(client.userId, Instant.ofEpochMilli(endDate), limit) - (users, groups) ← getDialogsUsersGroups(dialogs.toSeq) - } yield { - val stripEntities = optimizations.contains(ApiUpdateOptimization.STRIP_ENTITIES) - - Ok(ResponseLoadDialogs( - groups = if (stripEntities) Vector.empty else groups.toVector, - users = if (stripEntities) Vector.empty else users.toVector, - dialogs = dialogs.toVector, - userPeers = users.toVector map (u ⇒ ApiUserOutPeer(u.id, u.accessHash)), - groupPeers = groups.toVector map (g ⇒ ApiGroupOutPeer(g.id, g.accessHash)) - )) - } + ((users, userPeers), (groups, groupPeers)) ← usersAndGroupsByDialogs(dialogs.toSeq, stripEntities, loadGroupMembers) + } yield Ok(ResponseLoadDialogs( + groups = groups, + users = users, + dialogs = dialogs.toVector, + userPeers = userPeers, + groupPeers = groupPeers + )) } override def doHandleLoadGroupedDialogs( @@ -131,32 +127,27 @@ trait HistoryHandlers { clientData: ClientData ): Future[HandlerResult[ResponseLoadGroupedDialogs]] = authorized(clientData) { implicit client ⇒ + val stripEntities = optimizations contains ApiUpdateOptimization.STRIP_ENTITIES + val loadGroupMembers = !optimizations.contains(ApiUpdateOptimization.GROUPS_V2) + for { dialogGroups ← dialogExt.fetchApiGroupedDialogs(client.userId) - (userIds, groupIds) = dialogGroups.view.flatMap(_.dialogs).foldLeft((Seq.empty[Int], Seq.empty[Int])) { - case ((uids, gids), dialog) ⇒ - dialog.peer.`type` match { - case ApiPeerType.Group ⇒ (uids, gids :+ dialog.peer.id) - case ApiPeerType.Private ⇒ (uids :+ dialog.peer.id, gids) - } - } - // TODO: make like here: im.actor.server.api.rpc.service.groups.GroupsServiceImpl.usersOrPeers - (groups, users) ← GroupUtils.getGroupsUsers(groupIds, userIds, client.userId, client.authId) + ((users, userPeers), (groups, groupPeers)) ← usersAndGroupsByShortDialogs( + dialogs = dialogGroups.flatMap(_.dialogs), + stripEntities, + loadGroupMembers + ) archivedExist ← dialogExt.fetchArchivedDialogs(client.userId, None, 1) map (_._1.nonEmpty) showInvite ← db.run(UserContactRepo.count(client.userId)) map (_ < 5) - } yield { - val stripEntities = optimizations contains ApiUpdateOptimization.STRIP_ENTITIES - - Ok(ResponseLoadGroupedDialogs( - dialogs = dialogGroups, - users = if (stripEntities) Vector.empty else users.toVector, - groups = if (stripEntities) Vector.empty else groups.toVector, - showArchived = Some(archivedExist), - showInvite = Some(showInvite), - userPeers = if (stripEntities) users.toVector map (u ⇒ ApiUserOutPeer(u.id, u.accessHash)) else Vector.empty, - groupPeers = if (stripEntities) groups.toVector map (g ⇒ ApiGroupOutPeer(g.id, g.accessHash)) else Vector.empty - )) - } + } yield Ok(ResponseLoadGroupedDialogs( + dialogs = dialogGroups, + users = users, + groups = groups, + showArchived = Some(archivedExist), + showInvite = Some(showInvite), + userPeers = userPeers, + groupPeers = groupPeers + )) } override def doHandleHideDialog(peer: ApiOutPeer, clientData: ClientData): Future[HandlerResult[ResponseDialogsOrder]] = @@ -197,6 +188,9 @@ trait HistoryHandlers { authorized(clientData) { implicit client ⇒ withOutPeer(peer) { val modelPeer = peer.asModel + val stripEntities = optimizations.contains(ApiUpdateOptimization.STRIP_ENTITIES) + val loadGroupMembers = !optimizations.contains(ApiUpdateOptimization.GROUPS_V2) + val action = for { historyOwner ← DBIO.from(getHistoryOwner(modelPeer, client.userId)) (lastReceivedAt, lastReadAt) ← getLastReceiveReadDates(modelPeer) @@ -226,19 +220,14 @@ trait HistoryHandlers { case None ⇒ (msgs, uids, guids) } } - users ← DBIO.from(Future.sequence(userIds.toVector map (userExt.getApiStruct(_, client.userId, client.authId)))) - groups ← DBIO.from(Future.sequence(groupIds.toVector map (groupExt.getApiStruct(_, client.userId)))) - } yield { - val stripEntities = optimizations.contains(ApiUpdateOptimization.STRIP_ENTITIES) - - Ok(ResponseLoadHistory( - history = messages, - users = if (stripEntities) Vector.empty else users, - userPeers = users map (u ⇒ ApiUserOutPeer(u.id, u.accessHash)), - groups = if (stripEntities) Vector.empty else groups, - groupPeers = groups map (g ⇒ ApiGroupOutPeer(g.id, g.accessHash)) - )) - } + ((users, userPeers), (groups, groupPeers)) ← DBIO.from(usersAndGroupsByIds(groupIds, userIds, stripEntities, loadGroupMembers)) + } yield Ok(ResponseLoadHistory( + history = messages, + users = users, + userPeers = userPeers, + groups = groups, + groupPeers = groupPeers + )) db.run(action) } } @@ -316,51 +305,4 @@ trait HistoryHandlers { } yield (new DateTime(info.lastReceivedDate.toEpochMilli), new DateTime(info.lastReadDate.toEpochMilli))) } - private def getDialogsUsersGroups(dialogs: Seq[ApiDialog])(implicit client: AuthorizedClientData): Future[(Set[ApiUser], Set[ApiGroup])] = { - val (userIds, groupIds) = dialogs.foldLeft((Set.empty[Int], Set.empty[Int])) { - case ((uacc, gacc), dialog) ⇒ - if (dialog.peer.`type` == ApiPeerType.Private) { - (uacc ++ relatedUsers(dialog.message) ++ Set(dialog.peer.id, dialog.senderUserId), gacc) - } else { - (uacc ++ relatedUsers(dialog.message) + dialog.senderUserId, gacc + dialog.peer.id) - } - } - - for { - groups ← Future.sequence(groupIds map (groupExt.getApiStruct(_, client.userId))) - groupUserIds = groups.flatMap(g ⇒ g.members.flatMap(m ⇒ Seq(m.userId, m.inviterUserId)) :+ g.creatorUserId) - users ← Future.sequence((userIds ++ groupUserIds).filterNot(_ == 0) map (UserUtils.safeGetUser(_, client.userId, client.authId))) map (_.flatten) - } yield (users, groups) - } - - private def relatedUsers(message: ApiMessage): Set[Int] = { - message match { - case ApiServiceMessage(_, extOpt) ⇒ extOpt map relatedUsers getOrElse Set.empty - case ApiTextMessage(_, mentions, _) ⇒ mentions.toSet - case ApiJsonMessage(_) ⇒ Set.empty - case _: ApiEmptyMessage ⇒ Set.empty - case _: ApiDocumentMessage ⇒ Set.empty - case _: ApiStickerMessage ⇒ Set.empty - case _: ApiUnsupportedMessage ⇒ Set.empty - case _: ApiBinaryMessage ⇒ Set.empty - case _: ApiEncryptedMessage ⇒ Set.empty - } - } - - private def relatedUsers(ext: ApiServiceEx): Set[Int] = - ext match { - case ApiServiceExContactRegistered(userId) ⇒ Set(userId) - case ApiServiceExChangedAvatar(_) ⇒ Set.empty - case ApiServiceExChangedTitle(_) ⇒ Set.empty - case ApiServiceExChangedTopic(_) ⇒ Set.empty - case ApiServiceExChangedAbout(_) ⇒ Set.empty - case ApiServiceExGroupCreated | _: ApiServiceExGroupCreated ⇒ Set.empty - case ApiServiceExPhoneCall(_) ⇒ Set.empty - case ApiServiceExPhoneMissed | _: ApiServiceExPhoneMissed ⇒ Set.empty - case ApiServiceExUserInvited(invitedUserId) ⇒ Set(invitedUserId) - case ApiServiceExUserJoined | _: ApiServiceExUserJoined ⇒ Set.empty - case ApiServiceExUserKicked(kickedUserId) ⇒ Set(kickedUserId) - case ApiServiceExUserLeft | _: ApiServiceExUserLeft ⇒ Set.empty - case _: ApiServiceExChatArchived | _: ApiServiceExChatRestored ⇒ Set.empty - } } diff --git a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/search/SearchServiceImpl.scala b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/search/SearchServiceImpl.scala index 16ce7852a1..9b2128714d 100644 --- a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/search/SearchServiceImpl.scala +++ b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/search/SearchServiceImpl.scala @@ -76,29 +76,31 @@ class SearchServiceImpl(implicit system: ActorSystem) extends SearchService { text: Option[String], optimizations: IndexedSeq[ApiUpdateOptimization.Value] )(implicit client: AuthorizedClientData): Future[HandlerResult[ResponsePeerSearch]] = { + val stripEntities = optimizations.contains(ApiUpdateOptimization.STRIP_ENTITIES) + val loadGroupMembers = !optimizations.contains(ApiUpdateOptimization.GROUPS_V2) + for { results ← FutureExt.ftraverse(pts)(search(_, text)).map(_.reduce(_ ++ _)) - (groupIds, userIds, searchResults) = (results foldLeft (Vector.empty[Int], Vector.empty[Int], Vector.empty[ApiPeerSearchResult])) { + (groupIds, userIds, searchResults) = (results foldLeft (Set.empty[Int], Set.empty[Int], Vector.empty[ApiPeerSearchResult])) { case (acc @ (gids, uids, rslts), found @ ApiPeerSearchResult(peer, _)) ⇒ if (rslts.exists(_.peer == peer)) { acc } else { peer.`type` match { - case ApiPeerType.Private ⇒ (gids, uids :+ peer.id, rslts :+ found) - case ApiPeerType.Group ⇒ (gids :+ peer.id, uids, rslts :+ found) + case ApiPeerType.Private ⇒ (gids, uids + peer.id, rslts :+ found) + case ApiPeerType.Group ⇒ (gids + peer.id, uids, rslts :+ found) } } } - // TODO: make like here: im.actor.server.api.rpc.service.groups.GroupsServiceImpl.usersOrPeers - (groups, users) ← GroupUtils.getGroupsUsers(groupIds, userIds, client.userId, client.authId) + ((users, userPeers), (groups, groupPeers)) ← EntitiesHelpers.usersAndGroupsByIds(groupIds, userIds, stripEntities, loadGroupMembers) } yield { - val stripEntities = optimizations.contains(ApiUpdateOptimization.STRIP_ENTITIES) + Ok(ResponsePeerSearch( searchResults = searchResults, - users = if (stripEntities) Vector.empty else users.toVector, - groups = if (stripEntities) Vector.empty else groups.toVector, - userPeers = users.toVector map (u ⇒ ApiUserOutPeer(u.id, u.accessHash)), - groupPeers = groups.toVector map (g ⇒ ApiGroupOutPeer(g.id, g.accessHash)) + users = users, + groups = groups, + userPeers = userPeers, + groupPeers = groupPeers )) } } From 1003b2e539df52a87276dba55ee22a6c9ea2e9ce Mon Sep 17 00:00:00 2001 From: rockjam Date: Tue, 9 Aug 2016 20:18:20 +0300 Subject: [PATCH 264/414] fix(server:enrich): properly restart enricher on failure --- .../main/scala/im/actor/server/enrich/PreviewMaker.scala | 7 +++---- .../scala/im/actor/server/enrich/RichMessageWorker.scala | 9 ++++++--- .../scala/im/actor/server/enrich/PreviewMakerSpec.scala | 4 ++-- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/actor-server/actor-enrich/src/main/scala/im/actor/server/enrich/PreviewMaker.scala b/actor-server/actor-enrich/src/main/scala/im/actor/server/enrich/PreviewMaker.scala index 8062bb9de6..c5bf9dd624 100644 --- a/actor-server/actor-enrich/src/main/scala/im/actor/server/enrich/PreviewMaker.scala +++ b/actor-server/actor-enrich/src/main/scala/im/actor/server/enrich/PreviewMaker.scala @@ -7,12 +7,11 @@ import spray.http.HttpHeaders.`Content-Disposition` import spray.http.HttpMethods.GET import spray.http._ -import scala.concurrent.{ ExecutionContext, Future } +import scala.concurrent.Future object PreviewMaker { - def apply(config: RichMessageConfig, name: String)(implicit system: ActorSystem): ActorRef = - system.actorOf(Props(classOf[PreviewMaker], config), name) + def props(config: RichMessageConfig) = Props(classOf[PreviewMaker], config) object Failures { object Messages { @@ -66,4 +65,4 @@ class PreviewMaker(config: RichMessageConfig) extends Actor with ActorLogging wi result pipeTo sender() case _ ⇒ } -} \ No newline at end of file +} diff --git a/actor-server/actor-enrich/src/main/scala/im/actor/server/enrich/RichMessageWorker.scala b/actor-server/actor-enrich/src/main/scala/im/actor/server/enrich/RichMessageWorker.scala index b0ab3d83c1..457abb56cf 100644 --- a/actor-server/actor-enrich/src/main/scala/im/actor/server/enrich/RichMessageWorker.scala +++ b/actor-server/actor-enrich/src/main/scala/im/actor/server/enrich/RichMessageWorker.scala @@ -40,8 +40,6 @@ final class RichMessageWorker(config: RichMessageConfig) extends Actor with Acto override val log = Logging(system, this) - private val previewMaker = PreviewMaker(config, "previewMaker") - private val privateSubscribe = Subscribe(pubSubExt.privateMessagesTopic, groupId, self) private val publicSubscribe = Subscribe(pubSubExt.groupMessagesTopic, None, self) @@ -94,7 +92,7 @@ final class RichMessageWorker(config: RichMessageConfig) extends Actor with Acto updated = ApiDocumentMessage( fileId = location.fileId, accessHash = location.accessHash, - fileSize = imageBytes.size, + fileSize = imageBytes.length, name = fullName, mimeType = mimeType, thumb = Some(ApiFastThumb(thumb.width, thumb.height, thumbBytes)), @@ -106,4 +104,9 @@ final class RichMessageWorker(config: RichMessageConfig) extends Actor with Acto log.debug("failed to make preview for message with randomId: {}, cause: {} ", randomId, mess) } + private def previewMaker: ActorRef = { + val name = "preview-maker" + context.child(name).getOrElse(context.actorOf(PreviewMaker.props(config), name)) + } + } diff --git a/actor-server/actor-tests/src/test/scala/im/actor/server/enrich/PreviewMakerSpec.scala b/actor-server/actor-tests/src/test/scala/im/actor/server/enrich/PreviewMakerSpec.scala index aa83369c55..f0aadbbcd1 100644 --- a/actor-server/actor-tests/src/test/scala/im/actor/server/enrich/PreviewMakerSpec.scala +++ b/actor-server/actor-tests/src/test/scala/im/actor/server/enrich/PreviewMakerSpec.scala @@ -35,7 +35,7 @@ class PreviewMakerSpec extends BaseRichMessageSpec { implicit val probe = TestProbe() val config = RichMessageConfig(5 * 1024 * 1024) - val previewMaker = PreviewMaker(config, "previewMaker" + new DateTime) + val previewMaker = system.actorOf(PreviewMaker.props(config)) import PreviewMaker._ @@ -88,7 +88,7 @@ class PreviewMakerSpec extends BaseRichMessageSpec { def imageTooLarge() = { val image = Images.withNameHttp val config = RichMessageConfig(image.contentLength - 1000L) - val previewMaker = PreviewMaker(config, "previewMaker" + new DateTime) + val previewMaker = system.actorOf(PreviewMaker.props(config)) sendGetPreview(previewMaker, image.url) probe watch previewMaker From 107aab9a026f176334d4bfe31e54546aea51b1e4 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Tue, 9 Aug 2016 15:54:14 -0400 Subject: [PATCH 265/414] fix(iOS): fixing contact bubble --- .../Content/Conversation/Cell/AABubbleContactCell.swift | 4 ---- 1 file changed, 4 deletions(-) diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Cell/AABubbleContactCell.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Cell/AABubbleContactCell.swift index 56a80e21a0..e9fd6f2eef 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Cell/AABubbleContactCell.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Cell/AABubbleContactCell.swift @@ -199,10 +199,6 @@ public class AAContactCellLayout: AACellLayout { public class AABubbleContactCellLayouter: AABubbleLayouter { public func isSuitable(message: ACMessage) -> Bool { - if (!ActorSDK.sharedActor().enableExperimentalFeatures) { - return false - } - if (message.content is ACContactContent) { return true } From c7092e0a9b87b0ec20fb68d97a6d6b76d7113c5a Mon Sep 17 00:00:00 2001 From: rockjam Date: Tue, 9 Aug 2016 23:02:06 +0300 Subject: [PATCH 266/414] fix(server:groups): improve loadMembers time --- .../scala/im/actor/server/group/GroupProcessor.scala | 11 ++++++++++- .../im/actor/server/group/GroupQueryHandlers.scala | 8 +++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala index 4e68890fbf..0496cd7dc7 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala @@ -1,16 +1,18 @@ package im.actor.server.group import java.time.Instant +import java.util.concurrent.TimeUnit import akka.actor.{ ActorRef, ActorSystem, Props, ReceiveTimeout, Status } import akka.cluster.sharding.ShardRegion import akka.http.scaladsl.util.FastFuture +import com.github.benmanes.caffeine.cache.{ Cache, Caffeine } import im.actor.api.rpc.peers.{ ApiPeer, ApiPeerType } import im.actor.concurrent.ActorFutures import im.actor.serialization.ActorSerializer import im.actor.server.cqrs.{ Event, Processor, TaggedEvent } import im.actor.server.db.DbExtension -import im.actor.server.dialog.{ DialogEnvelope, DialogExtension } +import im.actor.server.dialog._ import im.actor.server.group.GroupErrors._ import im.actor.server.group.GroupCommands._ import im.actor.server.group.GroupQueries._ @@ -119,6 +121,13 @@ private[group] final class GroupProcessor protected var integrationStorage: IntegrationTokensWriteOps = _ protected val globalNamesStorage = new GlobalNamesStorageKeyValueStorage + // short living cache to store member's names when user loads group members + protected implicit val memberNamesCache: Cache[java.lang.Integer, Future[String]] = + Caffeine.newBuilder() + .expireAfterAccess(3, TimeUnit.MINUTES) + .maximumSize(Long.MaxValue) + .build[java.lang.Integer, Future[String]] + protected val groupId = self.path.name.toInt protected val apiGroupPeer = ApiPeer(ApiPeerType.Group, groupId) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala index 79c9396362..cf68390746 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala @@ -9,6 +9,7 @@ import im.actor.api.rpc.groups._ import im.actor.server.group.GroupErrors.{ IncorrectGroupType, NoPermission, NotOwner } import im.actor.server.group.GroupQueries._ import im.actor.server.group.GroupType.{ Channel, General, Unrecognized } +import im.actor.util.cache.CacheHelpers.withCachedFuture import scala.concurrent.Future @@ -52,7 +53,12 @@ trait GroupQueryHandlers { for { (members, nextOffset) ← Source(state.members) - .mapAsync(1)(member ⇒ userExt.getName(member._1, clientUserId) map (member._2 → _)) + .mapAsync(1) { + case (userId, member) ⇒ + withCachedFuture[java.lang.Integer, String](userId) { + userExt.getName(userId, clientUserId) + } map { name ⇒ member → name } + } .runFold(Vector.empty[(Member, String)])(_ :+ _) map { users ⇒ val tail = users.sortBy(_._2).map(_._1).drop(offset) val nextOffset = if (tail.length > limit) Some(Int32Value(offset + limit).toByteArray) else None From 72148460408ce9aa462bbfb4ba62b22dbc736310 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Tue, 9 Aug 2016 16:45:05 -0400 Subject: [PATCH 267/414] fix(core): Fixing compilation error --- .../main/java/im/actor/core/modules/search/SearchModule.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/search/SearchModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/search/SearchModule.java index 0fe4618dcd..99658ab19f 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/search/SearchModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/search/SearchModule.java @@ -32,6 +32,7 @@ import im.actor.core.modules.search.sources.GlobalSearchSource; import im.actor.runtime.Storage; import im.actor.runtime.actors.ActorRef; +import im.actor.runtime.actors.messages.Void; import im.actor.runtime.collections.ManagedList; import im.actor.runtime.mvvm.SearchValueModel; import im.actor.runtime.promise.Promise; @@ -42,6 +43,9 @@ public class SearchModule extends AbsModule { + // j2objc workaround + private static final Void DUMB = null; + private ListEngine searchList; private ActorRef actorRef; From cda283f1ac364bff4df759d84759796ad76012b4 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Wed, 10 Aug 2016 11:23:59 +0300 Subject: [PATCH 268/414] fix(android): remove sdk reference from AndroidMessenger --- .../controllers/conversation/attach/AttachFragment.java | 4 ++-- .../im/actor/sdk/controllers/share/ShareFragment.java | 3 ++- .../src/main/java/im/actor/core/AndroidMessenger.java | 8 ++------ 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/attach/AttachFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/attach/AttachFragment.java index af3804dd53..96a6df2ec0 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/attach/AttachFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/attach/AttachFragment.java @@ -331,12 +331,12 @@ protected void onItemClicked(int id) { @Override public void onUriPicked(Uri uri) { - execute(messenger().sendUri(getPeer(), uri)); + execute(messenger().sendUri(getPeer(), uri, ActorSDK.sharedActor().getAppName())); } protected void onUrisPicked(List uris) { for (Uri s : uris) { - execute(messenger().sendUri(getPeer(), s)); + execute(messenger().sendUri(getPeer(), s, ActorSDK.sharedActor().getAppName())); } } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/share/ShareFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/share/ShareFragment.java index 7769168e00..1d7bf0087d 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/share/ShareFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/share/ShareFragment.java @@ -16,6 +16,7 @@ import im.actor.core.entity.Peer; import im.actor.core.entity.PeerType; import im.actor.core.entity.content.AbsContent; +import im.actor.sdk.ActorSDK; import im.actor.sdk.R; import im.actor.sdk.controllers.Intents; import im.actor.sdk.controllers.dialogs.DialogsFragment; @@ -139,7 +140,7 @@ public void onPeerClicked(Peer peer) { messenger().sendMessage(peer, shareAction.getText()); } else if (shareAction.getUris().size() > 0) { for (String sendUri : shareAction.getUris()) { - executeSilent(messenger().sendUri(peer, Uri.parse(sendUri))); + executeSilent(messenger().sendUri(peer, Uri.parse(sendUri), ActorSDK.sharedActor().getAppName())); } } else if (shareAction.getUserId() != null) { String userName = users().get(shareAction.getUserId()).getName().get(); diff --git a/actor-sdk/sdk-core/core/core-android/src/main/java/im/actor/core/AndroidMessenger.java b/actor-sdk/sdk-core/core/core-android/src/main/java/im/actor/core/AndroidMessenger.java index 2f0601248a..98e4a62254 100644 --- a/actor-sdk/sdk-core/core/core-android/src/main/java/im/actor/core/AndroidMessenger.java +++ b/actor-sdk/sdk-core/core/core-android/src/main/java/im/actor/core/AndroidMessenger.java @@ -38,7 +38,6 @@ import im.actor.core.utils.IOUtils; import im.actor.core.utils.ImageHelper; import im.actor.core.viewmodel.Command; -import im.actor.core.viewmodel.CommandCallback; import im.actor.core.viewmodel.GalleryVM; import im.actor.runtime.Runtime; import im.actor.runtime.actors.Actor; @@ -49,10 +48,7 @@ import im.actor.runtime.android.AndroidContext; import im.actor.runtime.eventbus.EventBus; import im.actor.runtime.generic.mvvm.BindedDisplayList; -import im.actor.runtime.mvvm.Value; -import im.actor.runtime.mvvm.ValueChangedListener; import im.actor.core.utils.GalleryScannerActor; -import im.actor.sdk.ActorSDK; import me.leolin.shortcutbadger.ShortcutBadger; import static im.actor.runtime.actors.ActorSystem.system; @@ -311,7 +307,7 @@ public void sendVideo(Peer peer, String fullFilePath, String fileName) { } } - public Command sendUri(final Peer peer, final Uri uri) { + public Command sendUri(final Peer peer, final Uri uri, String appName) { return callback -> fileDownloader.execute(() -> { String[] filePathColumn = {MediaStore.Images.Media.DATA, MediaStore.Video.Media.MIME_TYPE, MediaStore.Video.Media.TITLE}; @@ -350,7 +346,7 @@ public Command sendUri(final Peer peer, final Uri uri) { String externalPath = externalFile.getAbsolutePath(); File dest = new File(externalPath + "/" + - ActorSDK.sharedActor().getAppName() + + appName + "/"); dest.mkdirs(); From ec1f17af1cf3c835f801e0300ada39c9ca990e53 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Wed, 10 Aug 2016 16:55:41 +0300 Subject: [PATCH 269/414] feat(android): add full global search --- .../search/GlobalSearchBaseFragment.java | 129 ++++++++---------- .../sdk/controllers/search/SearchAdapter.java | 47 ++++++- .../sdk/controllers/search/SearchHolder.java | 3 + .../src/main/res/values-ru/ui_text.xml | 2 +- .../generic/mvvm/BindedDisplayList.java | 3 + 5 files changed, 109 insertions(+), 75 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/search/GlobalSearchBaseFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/search/GlobalSearchBaseFragment.java index febdcc1844..9a21152a02 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/search/GlobalSearchBaseFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/search/GlobalSearchBaseFragment.java @@ -15,9 +15,9 @@ import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; -import android.widget.LinearLayout; import android.widget.TextView; +import java.util.ArrayList; import java.util.List; import im.actor.core.entity.Avatar; @@ -30,10 +30,10 @@ import im.actor.core.viewmodel.UserVM; import im.actor.runtime.generic.mvvm.BindedDisplayList; import im.actor.runtime.generic.mvvm.DisplayList; +import im.actor.runtime.generic.mvvm.alg.Modifications; import im.actor.sdk.ActorSDK; import im.actor.sdk.R; import im.actor.sdk.controllers.BaseFragment; -import im.actor.sdk.controllers.Intents; import im.actor.sdk.util.Screen; import im.actor.sdk.view.adapters.HeaderViewRecyclerAdapter; import im.actor.sdk.view.adapters.OnItemClickedListener; @@ -57,9 +57,9 @@ public abstract class GlobalSearchBaseFragment extends BaseFragment { private BindedDisplayList searchDisplay; private final DisplayList.Listener searchListener = () -> onSearchChanged(); - SearchHolder footerSearchHolder; private String searchQuery; - private LinearLayout footer; + private boolean scrolledToEnd = true; + private ArrayList globalSearchResults = new ArrayList<>(); public GlobalSearchBaseFragment() { setHasOptionsMenu(true); @@ -159,20 +159,25 @@ public boolean onQueryTextChange(String s) { if (s.trim().length() > 0) { String activeSearchQuery = searchQuery; searchDisplay.initSearch(s.trim().toLowerCase(), false); + scrolledToEnd = false; searchAdapter.setQuery(s.trim().toLowerCase()); + globalSearchResults.clear(); messenger().findPeers(s).start(new CommandCallback>() { @Override public void onResult(List res) { - int footerVisability = footer.getVisibility(); if (searchQuery.equals(activeSearchQuery)) { - boolean showResult = false; - Peer peer = null; - String name = null; - Avatar avatar = null; - if (res.size() > 0) { - PeerSearchEntity peerSearchEntity = res.get(0); - peer = peerSearchEntity.getPeer(); + int order = 0; + outer: + for (PeerSearchEntity pse : res) { + for (int i = 0; i < searchDisplay.getSize(); i++) { + if (searchDisplay.getItem(i).getPeer().equals(pse.getPeer())) { + continue outer; + } + } + Avatar avatar; + Peer peer = pse.getPeer(); + String name; if (peer.getPeerType() == PeerType.PRIVATE) { UserVM userVM = users().get(peer.getPeerId()); name = userVM.getName().get(); @@ -182,29 +187,19 @@ public void onResult(List res) { name = groupVM.getName().get(); avatar = groupVM.getAvatar().get(); } else { - return; - } - showResult = true; - for (int i = 0; i < searchDisplay.getSize(); i++) { - if (searchDisplay.getItem(i).getPeer().equals(peer)) - showResult = false; - break; - } - - if (peerSearchEntity.getOptMatchString() != null) { - name = peerSearchEntity.getOptMatchString(); + continue; } + String optMatchString = pse.getOptMatchString(); + globalSearchResults.add(new SearchEntity(pse.getPeer(), order++, avatar, optMatchString == null ? name : optMatchString)); } - if (showResult) { - footerSearchHolder.bind(new SearchEntity(peer, 0, avatar, name), activeSearchQuery, true); - showView(footer); - } else { - goneView(footer); + if (globalSearchResults.size() > 0) { + globalSearchResults.add(new SearchEntityHeader(order++)); } - } - if (footerVisability != footer.getVisibility()) { + checkGlobalSearch(); onSearchChanged(); + } + } @Override @@ -215,8 +210,6 @@ public void onError(Exception e) { } else { searchDisplay.initEmpty(); - goneView(footer); - } } return false; @@ -225,12 +218,15 @@ public void onError(Exception e) { } private void onSearchChanged() { + if (searchDisplay == null) { + return; + } if (!searchDisplay.isInSearchState()) { showView(searchHintView); goneView(searchEmptyView); } else { goneView(searchHintView); - if (searchDisplay.getSize() == 0 && footer.getVisibility() != View.VISIBLE) { + if (searchDisplay.getSize() == 0) { showView(searchEmptyView); } else { goneView(searchEmptyView); @@ -245,6 +241,18 @@ private void showSearch() { isSearchVisible = true; searchDisplay = messenger().buildSearchDisplayList(); + searchDisplay.setBindHook(new BindedDisplayList.BindHook() { + @Override + public void onScrolledToEnd() { + scrolledToEnd = true; + checkGlobalSearch(); + } + + @Override + public void onItemTouched(SearchEntity item) { + + } + }); searchAdapter = new SearchAdapter(getActivity(), searchDisplay, new OnItemClickedListener() { @Override public void onClicked(SearchEntity item) { @@ -264,46 +272,6 @@ public boolean onLongClicked(SearchEntity item) { header.setBackgroundColor(ActorSDK.sharedActor().style.getMainBackgroundColor()); recyclerAdapter.addHeaderView(header); - TextView footerTitle = new TextView(getActivity()); - footerTitle.setText(R.string.main_search_global_header); - footerTitle.setTextSize(16); - footerTitle.setPadding(Screen.dp(12), Screen.dp(8), 0, Screen.dp(8)); - footerTitle.setBackgroundColor(ActorSDK.sharedActor().style.getBackyardBackgroundColor()); - footerTitle.setTextColor(ActorSDK.sharedActor().style.getTextSecondaryColor()); - - footerSearchHolder = new SearchHolder(getActivity(), new OnItemClickedListener() { - @Override - public void onClicked(SearchEntity item) { - searchMenu.collapseActionView(); - int peerId = item.getPeer().getPeerId(); - switch (item.getPeer().getPeerType()) { - case PRIVATE: - startActivity(Intents.openPrivateDialog(peerId, true, getActivity())); - break; - - case GROUP: - startActivity(Intents.openGroupDialog(peerId, false, getActivity())); - break; - } - } - - @Override - public boolean onLongClicked(SearchEntity item) { - return false; - } - }); - View footerGlobalSearchView = footerSearchHolder.itemView; - - footer = new LinearLayout(getActivity()); - footer.setOrientation(LinearLayout.VERTICAL); - LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(Screen.getWidth(), ViewGroup.LayoutParams.WRAP_CONTENT); - footer.addView(footerTitle, params); - footer.addView(footerGlobalSearchView, params); - - footer.setVisibility(View.GONE); - - recyclerAdapter.addFooterView(footer); - searchList.setAdapter(recyclerAdapter); searchDisplay.addListener(searchListener); showView(searchHintView, false); @@ -317,6 +285,12 @@ public boolean onLongClicked(SearchEntity item) { } } + private void checkGlobalSearch() { + if ((scrolledToEnd || searchDisplay.getSize() == 0) && globalSearchResults.size() > 0) { + searchDisplay.editList(Modifications.addLoadMore(globalSearchResults)); + } + } + private void hideSearch() { if (!isSearchVisible) { return; @@ -343,5 +317,14 @@ private void hideSearch() { } } + public class SearchEntityHeader extends SearchEntity { + + public SearchEntityHeader(int order) { + super(Peer.group(0), order, null, ""); + } + + + } + protected abstract void onPeerPicked(Peer peer); } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/search/SearchAdapter.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/search/SearchAdapter.java index fa9e3c049c..749ac79e63 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/search/SearchAdapter.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/search/SearchAdapter.java @@ -1,10 +1,15 @@ package im.actor.sdk.controllers.search; import android.content.Context; +import android.support.v7.widget.RecyclerView; import android.view.ViewGroup; +import android.widget.TextView; import im.actor.core.entity.SearchEntity; import im.actor.runtime.generic.mvvm.BindedDisplayList; +import im.actor.sdk.ActorSDK; +import im.actor.sdk.R; +import im.actor.sdk.util.Screen; import im.actor.sdk.view.adapters.OnItemClickedListener; import im.actor.runtime.android.view.BindedListAdapter; @@ -25,13 +30,53 @@ public void setQuery(String query) { this.query = query; } + @Override + public int getItemViewType(int position) { + SearchEntity e = getItem(position); + if (e instanceof GlobalSearchBaseFragment.SearchEntityHeader) { + return 1; + } else { + return super.getItemViewType(position); + } + } + @Override public SearchHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) { - return new SearchHolder(context, onItemClickedListener); + switch (viewType) { + case 1: + return new SearchHolderEx(context, onItemClickedListener); + default: + case 0: + return new SearchHolder(context, onItemClickedListener); + } } @Override public void onBindViewHolder(SearchHolder dialogHolder, int index, SearchEntity item) { dialogHolder.bind(item, query, index == getItemCount() - 1); } + + public class SearchHolderEx extends SearchHolder { + public SearchHolderEx(Context context, OnItemClickedListener clickedListener) { + super(context, clickedListener); + } + + @Override + protected void init(Context context, OnItemClickedListener clickedListener) { + itemView.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT)); + itemView.setBackgroundColor(ActorSDK.sharedActor().style.getBackyardBackgroundColor()); + TextView globalSearchTitle = new TextView(context); + globalSearchTitle.setText(R.string.main_search_global_header); + globalSearchTitle.setTextSize(16); + globalSearchTitle.setPadding(Screen.dp(12), Screen.dp(8), 0, Screen.dp(8)); + globalSearchTitle.setBackgroundColor(ActorSDK.sharedActor().style.getBackyardBackgroundColor()); + globalSearchTitle.setTextColor(ActorSDK.sharedActor().style.getTextSecondaryColor()); + ((ViewGroup) itemView).addView(globalSearchTitle); + } + + @Override + public void bind(SearchEntity entity, String query, boolean isLast) { + } + } } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/search/SearchHolder.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/search/SearchHolder.java index 89cb375049..da08eb4f5d 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/search/SearchHolder.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/search/SearchHolder.java @@ -30,7 +30,10 @@ public class SearchHolder extends BindedViewHolder { public SearchHolder(Context context, final OnItemClickedListener clickedListener) { super(new FrameLayout(context)); + init(context, clickedListener); + } + protected void init(Context context, final OnItemClickedListener clickedListener) { itemView.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-ru/ui_text.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-ru/ui_text.xml index 5149fa8b3d..8f4b3238ec 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-ru/ui_text.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-ru/ui_text.xml @@ -149,7 +149,7 @@ Начните набирать для поиска - Общий поиск + Глобальный поиск Ничего не найдено Помощь Поиск diff --git a/actor-sdk/sdk-core/runtime/runtime-generic/src/main/java/im/actor/runtime/generic/mvvm/BindedDisplayList.java b/actor-sdk/sdk-core/runtime/runtime-generic/src/main/java/im/actor/runtime/generic/mvvm/BindedDisplayList.java index 4ef8c4666c..ef71ef2c98 100644 --- a/actor-sdk/sdk-core/runtime/runtime-generic/src/main/java/im/actor/runtime/generic/mvvm/BindedDisplayList.java +++ b/actor-sdk/sdk-core/runtime/runtime-generic/src/main/java/im/actor/runtime/generic/mvvm/BindedDisplayList.java @@ -353,6 +353,9 @@ public void onLoaded(List items, long topSortKey, long bottomSortKey) { if (items.size() == 0) { window.onForwardCompleted(); + if (bindHook != null) { + bindHook.onScrolledToEnd(); + } // Log.d(TAG, "isLoadMoreForwardRequested = false: sync"); isLoadMoreForwardRequested = false; } else { From 12826de1be532001c8269b7588c36c1876ee2a64 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Wed, 10 Aug 2016 18:53:32 +0300 Subject: [PATCH 270/414] fix(core): add newly crated group to search --- .../core/modules/messaging/dialogs/DialogsActor.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/dialogs/DialogsActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/dialogs/DialogsActor.java index 58e8001e82..ef3e642582 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/dialogs/DialogsActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/dialogs/DialogsActor.java @@ -98,7 +98,7 @@ private Promise onMessage(Peer peer, Message message, boolean forceWrite, } boolean forceUpdate = false; - + boolean needUpdateSearch = false; if (dialog != null) { // Ignore old messages if no force if (!forceWrite && dialog.getSortDate() > message.getSortDate()) { @@ -125,11 +125,15 @@ private Promise onMessage(Peer peer, Message message, boolean forceWrite, builder.setPeer(peer) .setSortKey(message.getSortDate()); - + needUpdateSearch = true; forceUpdate = true; } - addOrUpdateItem(builder.createDialog()); + Dialog dialog1 = builder.createDialog(); + addOrUpdateItem(dialog1); + if (needUpdateSearch) { + updateSearch(dialog1); + } notifyState(forceUpdate); } From 5aa0c592f91d91c00e8c9e034b5bd2df601b88e3 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Wed, 10 Aug 2016 19:02:05 +0300 Subject: [PATCH 271/414] fix(android): hide global search on pause --- .../sdk/controllers/search/GlobalSearchBaseFragment.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/search/GlobalSearchBaseFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/search/GlobalSearchBaseFragment.java index 9a21152a02..ec03463fb1 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/search/GlobalSearchBaseFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/search/GlobalSearchBaseFragment.java @@ -326,5 +326,11 @@ public SearchEntityHeader(int order) { } + @Override + public void onPause() { + super.onPause(); + hideSearch(); + } + protected abstract void onPeerPicked(Peer peer); } From a28ed4c9031a57d903fa869a718a30426dea7285 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Thu, 11 Aug 2016 13:33:25 +0300 Subject: [PATCH 272/414] fix(android): sendUri fallback --- .../src/main/java/im/actor/core/AndroidMessenger.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/actor-sdk/sdk-core/core/core-android/src/main/java/im/actor/core/AndroidMessenger.java b/actor-sdk/sdk-core/core/core-android/src/main/java/im/actor/core/AndroidMessenger.java index 98e4a62254..8201f0d137 100644 --- a/actor-sdk/sdk-core/core/core-android/src/main/java/im/actor/core/AndroidMessenger.java +++ b/actor-sdk/sdk-core/core/core-android/src/main/java/im/actor/core/AndroidMessenger.java @@ -307,6 +307,10 @@ public void sendVideo(Peer peer, String fullFilePath, String fileName) { } } + public Command sendUri(final Peer peer, final Uri uri) { + return sendUri(peer, uri, "Actor"); + } + public Command sendUri(final Peer peer, final Uri uri, String appName) { return callback -> fileDownloader.execute(() -> { String[] filePathColumn = {MediaStore.Images.Media.DATA, MediaStore.Video.Media.MIME_TYPE, From e8e31d444d97c27971458d25fe3a2d601882eb9c Mon Sep 17 00:00:00 2001 From: kor_ka Date: Thu, 11 Aug 2016 14:37:47 +0300 Subject: [PATCH 273/414] fix(android): search: nickname/short name highlight, avatar blink --- .../controllers/search/GlobalSearchBaseFragment.java | 10 ++++++++++ .../im/actor/sdk/controllers/search/SearchHolder.java | 5 ++++- .../main/java/im/actor/sdk/view/SearchHighlight.java | 2 +- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/search/GlobalSearchBaseFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/search/GlobalSearchBaseFragment.java index ec03463fb1..b2c7186bd8 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/search/GlobalSearchBaseFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/search/GlobalSearchBaseFragment.java @@ -8,6 +8,7 @@ import android.support.v7.widget.ChatLinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.SearchView; +import android.support.v7.widget.SimpleItemAnimator; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -273,6 +274,14 @@ public boolean onLongClicked(SearchEntity item) { recyclerAdapter.addHeaderView(header); searchList.setAdapter(recyclerAdapter); + + RecyclerView.ItemAnimator animator = searchList.getItemAnimator(); + + if (animator instanceof SimpleItemAnimator) { + ((SimpleItemAnimator) animator).setSupportsChangeAnimations(false); + } + + searchDisplay.addListener(searchListener); showView(searchHintView, false); goneView(searchEmptyView, false); @@ -303,6 +312,7 @@ private void hideSearch() { } searchAdapter = null; searchList.setAdapter(null); + searchQuery = null; goneView(searchContainer, false); if (searchMenu != null) { diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/search/SearchHolder.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/search/SearchHolder.java index da08eb4f5d..abb6297ebc 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/search/SearchHolder.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/search/SearchHolder.java @@ -86,9 +86,12 @@ public void onClick(View v) { } public void bind(SearchEntity entity, String query, boolean isLast) { + boolean needRebind = this.entity == null || !entity.getPeer().equals(this.entity.getPeer()); this.entity = entity; - avatar.bind(entity.getAvatar(), entity.getTitle(), entity.getPeer().getPeerId()); + if (needRebind) { + avatar.bind(entity.getAvatar(), entity.getTitle(), entity.getPeer().getPeerId()); + } if (query != null) { title.setText(SearchHighlight.highlightQuery(entity.getTitle(), query, highlightColor)); } else { diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/SearchHighlight.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/SearchHighlight.java index c752449112..75a5e0bacb 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/SearchHighlight.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/SearchHighlight.java @@ -13,7 +13,7 @@ public class SearchHighlight { public static Spannable highlightQuery(String src, String query, int color) { - String matchString = src.toLowerCase(); + String matchString = src.toLowerCase().replace("@", " "); SpannableStringBuilder builder = new SpannableStringBuilder(src); if (matchString.startsWith(query)) { builder.setSpan(new ForegroundColorSpan(color), 0, query.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE); From 829c04db153372084bf9ba07a0a02160dabba54f Mon Sep 17 00:00:00 2001 From: kor_ka Date: Thu, 11 Aug 2016 17:02:30 +0300 Subject: [PATCH 274/414] fix(android): DialogView avatar - default rounding method --- .../im/actor/sdk/controllers/dialogs/view/DialogView.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/view/DialogView.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/view/DialogView.java index dba1098755..146cc812a2 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/view/DialogView.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/view/DialogView.java @@ -106,9 +106,7 @@ protected void initStyles() { GenericDraweeHierarchy hierarchy = new GenericDraweeHierarchyBuilder(getResources()) .setFadeDuration(0) .setRoundingParams(new RoundingParams() - .setRoundAsCircle(true) - .setRoundingMethod(RoundingParams.RoundingMethod.OVERLAY_COLOR) - .setOverlayColor(ActorSDK.sharedActor().style.getMainBackgroundColor())) + .setRoundAsCircle(true)) .build(); draweeHolder = DraweeHolder.create(hierarchy, getContext()); draweeHolder.getTopLevelDrawable().setCallback(this); From 46001670fb723eeb7b17879c20d44a86f8a6d2c0 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Thu, 11 Aug 2016 21:10:07 +0300 Subject: [PATCH 275/414] chore(android): call fragment in delegate --- .../java/im/actor/sdk/ActorSDKDelegate.java | 9 ++ .../im/actor/sdk/BaseActorSDKDelegate.java | 6 + .../sdk/controllers/calls/CallActivity.java | 7 +- .../sdk/controllers/calls/CallFragment.java | 135 +++++++++--------- 4 files changed, 91 insertions(+), 66 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorSDKDelegate.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorSDKDelegate.java index f79148c74e..781e6fc217 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorSDKDelegate.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorSDKDelegate.java @@ -62,6 +62,15 @@ public interface ActorSDKDelegate { @Nullable Fragment fragmentForProfile(int uid); + /** + * If not null returned, overrides call fragment + * + * @param callId call id + * @return Fragment + */ + @Nullable + Fragment fragmentForCall(long callId); + /** * If not null returned, overrides group info fragment * diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/BaseActorSDKDelegate.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/BaseActorSDKDelegate.java index a098b7b8a2..eb6e8396f8 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/BaseActorSDKDelegate.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/BaseActorSDKDelegate.java @@ -54,6 +54,12 @@ public Fragment fragmentForProfile(int uid) { return null; } + @Nullable + @Override + public Fragment fragmentForCall(long callId) { + return null; + } + @Override public Fragment fragmentForGroupInfo(int gid) { return null; diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/calls/CallActivity.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/calls/CallActivity.java index d2c1f0a46e..539d7041e4 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/calls/CallActivity.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/calls/CallActivity.java @@ -3,6 +3,7 @@ import android.graphics.Color; import android.graphics.drawable.ColorDrawable; import android.os.Bundle; +import android.support.v4.app.Fragment; import android.view.Menu; import android.view.WindowManager; @@ -44,7 +45,11 @@ protected void onCreate(Bundle savedInstanceState) { if (savedInstanceState == null) { callId = getIntent().getLongExtra("callId", -1); - showFragment(new CallFragment(callId), false); + Fragment callFragment = ActorSDK.sharedActor().getDelegate().fragmentForCall(callId); + if (callFragment == null) { + callFragment = CallFragment.create(callId); + } + showFragment(callFragment, false); } } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/calls/CallFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/calls/CallFragment.java index ad74e8dbf0..f0f2a9fa96 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/calls/CallFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/calls/CallFragment.java @@ -76,82 +76,87 @@ public class CallFragment extends BaseFragment { - private static final int PERMISSIONS_REQUEST_FOR_CALL = 147; - private static final int NOTIFICATION_ID = 2; - private static final int TIMER_ID = 1; - - private final ActorBinder ACTIVITY_BINDER = new ActorBinder(); - - private long callId = -1; - private Peer peer; - - private Vibrator v; - private View answerContainer; - private Ringtone ringtone; - private CallVM call; - - private AvatarView avatarView; - private TextView nameTV; - private ActorRef timer; - private TextView statusTV; - private View[] avatarLayers; - private View layer1; - private View layer2; - private View layer3; - - private NotificationManager manager; - private CallState currentState; - private ImageButton endCall; - private View endCallContainer; - private boolean speakerOn = false; - private AudioManager audioManager; - - private RecyclerListView membersList; - - private float dX, dY; - - private TintImageView muteCall; - private TextView muteCallTv; - private TintImageView speaker; - private TextView speakerTV; - private TintImageView videoIcon; - private TextView videoTv; + protected static final int PERMISSIONS_REQUEST_FOR_CALL = 147; + protected static final int NOTIFICATION_ID = 2; + protected static final int TIMER_ID = 1; + + protected final ActorBinder ACTIVITY_BINDER = new ActorBinder(); + + protected long callId = -1; + protected Peer peer; + + protected Vibrator v; + protected View answerContainer; + protected Ringtone ringtone; + protected CallVM call; + + protected AvatarView avatarView; + protected TextView nameTV; + protected ActorRef timer; + protected TextView statusTV; + protected View[] avatarLayers; + protected View layer1; + protected View layer2; + protected View layer3; + + protected NotificationManager manager; + protected CallState currentState; + protected ImageButton endCall; + protected View endCallContainer; + protected boolean speakerOn = false; + protected AudioManager audioManager; + + protected RecyclerListView membersList; + + protected float dX, dY; + + protected TintImageView muteCall; + protected TextView muteCallTv; + protected TintImageView speaker; + protected TextView speakerTV; + protected TintImageView videoIcon; + protected TextView videoTv; // // Video References // - private EglBase eglContext; + protected EglBase eglContext; - private SurfaceViewRenderer localVideoView; - private VideoRenderer localRender; - private boolean isLocalViewConfigured; - private VideoTrack localTrack; + protected SurfaceViewRenderer localVideoView; + protected VideoRenderer localRender; + protected boolean isLocalViewConfigured; + protected VideoTrack localTrack; - private SurfaceViewRenderer remoteVideoView; - private VideoRenderer remoteRender; - private boolean isRemoteViewConfigured; - private VideoTrack remoteTrack; + protected SurfaceViewRenderer remoteVideoView; + protected VideoRenderer remoteRender; + protected boolean isRemoteViewConfigured; + protected VideoTrack remoteTrack; // // Vibrate/tone/wakelock // boolean vibrate = true; - private PowerManager powerManager; - private PowerManager.WakeLock wakeLock; - private int field = 0x00000020; + protected PowerManager powerManager; + protected PowerManager.WakeLock wakeLock; + protected int field = 0x00000020; // // Constructor // - public CallFragment() { - manager = (NotificationManager) getActivity().getSystemService(Context.NOTIFICATION_SERVICE); + static CallFragment create(long callId) { + CallFragment res = new CallFragment(); + Bundle args = new Bundle(); + args.putLong("call_id", callId); + res.setArguments(args); + return res; } - public CallFragment(long callId) { - this.callId = callId; + public CallFragment() { + manager = (NotificationManager) getActivity().getSystemService(Context.NOTIFICATION_SERVICE); + this.callId = getArguments().getLong("call_id"); this.call = messenger().getCall(callId); if (call == null) { this.peer = Peer.user(myUid()); @@ -389,7 +394,7 @@ public void switchAvatarMembers() { } } - private void startTimer() { + protected void startTimer() { final DateFormat formatter = new SimpleDateFormat("HH:mm:ss"); formatter.setTimeZone(TimeZone.getTimeZone("UTC")); @@ -421,7 +426,7 @@ public void onRequestPermissionsResult(int requestCode, String[] permissions, in } } - private void initIncoming() { + protected void initIncoming() { getActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD | @@ -449,7 +454,7 @@ private void initIncoming() { }).start(); } - private void onAnswer() { + protected void onAnswer() { endCallContainer.setVisibility(View.VISIBLE); answerContainer.setVisibility(View.GONE); @@ -460,7 +465,7 @@ private void onAnswer() { messenger().answerCall(callId); } - private void doEndCall() { + protected void doEndCall() { messenger().endCall(callId); onCallEnd(); } @@ -778,7 +783,7 @@ public void onPause() { class CallMembersAdapter extends HolderAdapter { - private ArrayList members; + protected ArrayList members; protected CallMembersAdapter(Context context, final ValueModel> members) { super(context); @@ -810,12 +815,12 @@ protected ViewHolder createHolder(CallMember obj) { return new MemberHolder(); } - private class MemberHolder extends ViewHolder { + protected class MemberHolder extends ViewHolder { CallMember data; - private TextView userName; - private TextView status; - private AvatarView avatarView; + protected TextView userName; + protected TextView status; + protected AvatarView avatarView; @Override public View init(final CallMember data, ViewGroup viewGroup, Context context) { From a3a9588b01895cc517fa347ea86a12661cd49c59 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Thu, 11 Aug 2016 22:25:33 -0400 Subject: [PATCH 276/414] fix(iOS): Fixing shadows --- .../Conversation/Cell/AABubbleCell.swift | 67 ++++++------------- 1 file changed, 19 insertions(+), 48 deletions(-) diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Cell/AABubbleCell.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Cell/AABubbleCell.swift index 98954e7c0a..518930906e 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Cell/AABubbleCell.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Cell/AABubbleCell.swift @@ -181,6 +181,9 @@ public class AABubbleCell: UICollectionViewCell { contentView.addSubview(bubble) contentView.addSubview(bubbleBorder) + if appStyle.bubbleShadowEnabled { + contentView.addSubview(bubbleShadow) + } contentView.addSubview(newMessage) contentView.addSubview(dateBg) contentView.addSubview(dateText) @@ -298,61 +301,45 @@ public class AABubbleCell: UICollectionViewCell { switch(type) { case BubbleType.TextIn: if (isCompact) { - bubbleShadow.image = AABubbleCell.cachedInTextCompactBgShadow - bubbleShadow.highlightedImage = AABubbleCell.cachedInTextCompactBgShadow bubble.image = AABubbleCell.cachedInTextCompactBg bubbleBorder.image = AABubbleCell.cachedInTextCompactBgBorder - bubble.highlightedImage = AABubbleCell.cachedInTextCompactSelectedBg - bubbleBorder.highlightedImage = AABubbleCell.cachedInTextCompactBgBorder + bubbleShadow.image = AABubbleCell.cachedInTextCompactBgShadow } else { - bubbleShadow.image = AABubbleCell.cachedInTextBgShadow - bubbleShadow.highlightedImage = AABubbleCell.cachedInTextBgShadow bubble.image = AABubbleCell.cachedInTextBg bubbleBorder.image = AABubbleCell.cachedInTextBgBorder - bubble.highlightedImage = AABubbleCell.cachedInTextBg - bubbleBorder.highlightedImage = AABubbleCell.cachedInTextBgBorder + bubbleShadow.image = AABubbleCell.cachedInTextBgShadow } break case BubbleType.TextOut: if (isCompact) { - bubbleShadow.image = AABubbleCell.cachedOutTextCompactBgShadow - bubbleShadow.highlightedImage = AABubbleCell.cachedOutTextCompactBgShadow bubble.image = AABubbleCell.cachedOutTextCompactBg bubbleBorder.image = AABubbleCell.cachedOutTextCompactBgBorder - bubble.highlightedImage = AABubbleCell.cachedOutTextCompactSelectedBg - bubbleBorder.highlightedImage = AABubbleCell.cachedOutTextCompactBgBorder + bubbleShadow.image = AABubbleCell.cachedOutTextCompactBgShadow } else { - bubbleShadow.image = AABubbleCell.cachedOutTextBgShadow - bubbleShadow.highlightedImage = AABubbleCell.cachedOutTextBgShadow bubble.image = AABubbleCell.cachedOutTextBg bubbleBorder.image = AABubbleCell.cachedOutTextBgBorder - bubble.highlightedImage = AABubbleCell.cachedOutTextBg - bubbleBorder.highlightedImage = AABubbleCell.cachedOutTextBgBorder + bubbleShadow.image = AABubbleCell.cachedOutTextBgShadow } break case BubbleType.MediaIn: bubble.image = AABubbleCell.cachedMediaBg bubbleBorder.image = AABubbleCell.cachedMediaBgBorder - bubble.highlightedImage = AABubbleCell.cachedMediaBg - bubbleBorder.highlightedImage = AABubbleCell.cachedMediaBgBorder + bubbleShadow.image = nil break case BubbleType.MediaOut: bubble.image = AABubbleCell.cachedMediaBg bubbleBorder.image = AABubbleCell.cachedMediaBgBorder - bubble.highlightedImage = AABubbleCell.cachedMediaBg - bubbleBorder.highlightedImage = AABubbleCell.cachedMediaBgBorder + bubbleShadow.image = nil break case BubbleType.Service: bubble.image = AABubbleCell.cachedServiceBg bubbleBorder.image = nil - bubble.highlightedImage = AABubbleCell.cachedServiceBg - bubbleBorder.highlightedImage = nil + bubbleShadow.image = nil break case BubbleType.Sticker: bubble.image = nil; bubbleBorder.image = nil - bubble.highlightedImage = nil; - bubbleBorder.highlightedImage = nil + bubbleShadow.image = nil break } } @@ -362,61 +349,45 @@ public class AABubbleCell: UICollectionViewCell { switch (type) { case BubbleType.TextIn: if (!isFullSize!) { - bubbleShadow.image = AABubbleCell.cachedInTextCompactBgShadow - // bubbleShadow.highlightedImage = AABubbleCell.cachedInTextCompactBgShadow bubble.image = AABubbleCell.cachedInTextCompactBg bubbleBorder.image = AABubbleCell.cachedInTextCompactBgBorder - // bubble.highlightedImage = AABubbleCell.cachedInTextCompactSelectedBg - // bubbleBorder.highlightedImage = AABubbleCell.cachedInTextCompactBgBorder + bubbleShadow.image = AABubbleCell.cachedInTextCompactBgShadow } else { - bubbleShadow.image = AABubbleCell.cachedInTextBgShadow - // bubbleShadow.highlightedImage = AABubbleCell.cachedInTextBgShadow bubble.image = AABubbleCell.cachedInTextBg bubbleBorder.image = AABubbleCell.cachedInTextBgBorder - // bubble.highlightedImage = AABubbleCell.cachedInTextBg - // bubbleBorder.highlightedImage = AABubbleCell.cachedInTextBgBorder + bubbleShadow.image = AABubbleCell.cachedInTextBgShadow } break case BubbleType.TextOut: if (!isFullSize!) { - bubbleShadow.image = AABubbleCell.cachedOutTextCompactBgShadow - // bubbleShadow.highlightedImage = AABubbleCell.cachedOutTextCompactBgShadow bubble.image = AABubbleCell.cachedOutTextCompactBg bubbleBorder.image = AABubbleCell.cachedOutTextCompactBgBorder - // bubble.highlightedImage = AABubbleCell.cachedOutTextCompactSelectedBg - // bubbleBorder.highlightedImage = AABubbleCell.cachedOutTextCompactBgBorder + bubbleShadow.image = AABubbleCell.cachedOutTextCompactBgShadow } else { - bubbleShadow.image = AABubbleCell.cachedOutTextBgShadow - // bubbleShadow.highlightedImage = AABubbleCell.cachedOutTextBgShadow bubble.image = AABubbleCell.cachedOutTextBg bubbleBorder.image = AABubbleCell.cachedOutTextBgBorder - // bubble.highlightedImage = AABubbleCell.cachedOutTextBg - // bubbleBorder.highlightedImage = AABubbleCell.cachedOutTextBgBorder + bubbleShadow.image = AABubbleCell.cachedOutTextBgShadow } break case BubbleType.MediaIn: bubble.image = AABubbleCell.cachedMediaBg bubbleBorder.image = AABubbleCell.cachedMediaBgBorder - // bubble.highlightedImage = AABubbleCell.cachedMediaBg - // bubbleBorder.highlightedImage = AABubbleCell.cachedMediaBgBorder + bubbleShadow.image = nil break case BubbleType.MediaOut: bubble.image = AABubbleCell.cachedMediaBg bubbleBorder.image = AABubbleCell.cachedMediaBgBorder - // bubble.highlightedImage = AABubbleCell.cachedMediaBg - // bubbleBorder.highlightedImage = AABubbleCell.cachedMediaBgBorder + bubbleShadow.image = nil break case BubbleType.Service: bubble.image = AABubbleCell.cachedServiceBg bubbleBorder.image = nil - // bubble.highlightedImage = AABubbleCell.cachedServiceBg - // bubbleBorder.highlightedImage = nil + bubbleShadow.image = nil break case BubbleType.Sticker: bubble.image = nil; bubbleBorder.image = nil - // bubble.highlightedImage = nil; - // bubbleBorder.highlightedImage = nil + bubbleShadow.image = nil break } } From 8fc3a2356a49c6e4f6818e08d5b2255c2df3c72f Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Thu, 11 Aug 2016 22:27:57 -0400 Subject: [PATCH 277/414] fix(iOS): Fixing iOS order --- .../Controllers/Content/Conversation/Cell/AABubbleCell.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Cell/AABubbleCell.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Cell/AABubbleCell.swift index 518930906e..ee88447ff4 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Cell/AABubbleCell.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Cell/AABubbleCell.swift @@ -179,11 +179,11 @@ public class AABubbleCell: UICollectionViewCell { contentView.transform = CGAffineTransformMake(1, 0, 0, -1, 0, 0) - contentView.addSubview(bubble) - contentView.addSubview(bubbleBorder) if appStyle.bubbleShadowEnabled { contentView.addSubview(bubbleShadow) } + contentView.addSubview(bubble) + contentView.addSubview(bubbleBorder) contentView.addSubview(newMessage) contentView.addSubview(dateBg) contentView.addSubview(dateText) From 24fc309ee7180a190a6b3d127c300caeb72a07c3 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Thu, 11 Aug 2016 22:31:20 -0400 Subject: [PATCH 278/414] fix(iOS): Fixing decline button location --- .../Sources/Controllers/Calls/AACallViewController.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Calls/AACallViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Calls/AACallViewController.swift index e3334dce96..d843dba394 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Calls/AACallViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Calls/AACallViewController.swift @@ -187,7 +187,8 @@ public class AACallViewController: AAViewController, RTCEAGLVideoViewDelegate { answerCallButtonText.under(answerCallButton.frame, offset: 5) } if !declineCallButton.hidden { - declineCallButton.frame = CGRectMake((self.view.width - 72) / 2, self.view.height - 72 - 49, 72, 72) + // declineCallButton.frame = CGRectMake((self.view.width - 72) / 2, self.view.height - 72 - 49, 72, 72) + declineCallButton.frame = CGRectMake(self.view.width - 72 - 25, self.view.height - 72 - 49, 72, 72) declineCallButtonText.under(declineCallButton.frame, offset: 5) } } From a4f6aeef4f75e52c731bf83958a465f23557a5d6 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Fri, 12 Aug 2016 16:10:17 +0300 Subject: [PATCH 279/414] fix(android): call fragment init notification service after activity attached --- .../im/actor/sdk/controllers/calls/CallFragment.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/calls/CallFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/calls/CallFragment.java index f0f2a9fa96..2fdeb61e3b 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/calls/CallFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/calls/CallFragment.java @@ -155,7 +155,11 @@ static CallFragment create(long callId) { } public CallFragment() { - manager = (NotificationManager) getActivity().getSystemService(Context.NOTIFICATION_SERVICE); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { this.callId = getArguments().getLong("call_id"); this.call = messenger().getCall(callId); if (call == null) { @@ -163,11 +167,6 @@ public CallFragment() { } else { this.peer = call.getPeer(); } - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { manager = (NotificationManager) getActivity().getSystemService(Context.NOTIFICATION_SERVICE); From 67a01152b7ae554c9a03f3b86daf368ba2221c32 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Mon, 15 Aug 2016 15:21:19 +0300 Subject: [PATCH 280/414] feat(core + android): handle raw updates --- .../src/main/java/im/actor/sdk/ActorSDK.java | 6 +++ .../java/im/actor/sdk/ActorSDKDelegate.java | 8 +++- .../im/actor/sdk/BaseActorSDKDelegate.java | 6 +++ .../core/AndroidRawUpdateHandlerProvider.java | 12 +++++ .../java/im/actor/core/Configuration.java | 14 ++++++ .../im/actor/core/ConfigurationBuilder.java | 16 +++++++ .../java/im/actor/core/RawUpdatesHandler.java | 18 +++++++ .../actor/core/modules/raw/RawProcessor.java | 48 +++++++++++++++++++ .../sequence/processor/UpdateProcessor.java | 4 +- .../providers/RawUpdatesHandlerProvider.java | 8 ++++ 10 files changed, 138 insertions(+), 2 deletions(-) create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/core/AndroidRawUpdateHandlerProvider.java create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/RawUpdatesHandler.java create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/raw/RawProcessor.java create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/providers/RawUpdatesHandlerProvider.java diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorSDK.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorSDK.java index 5e90a78673..723cfd1057 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorSDK.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorSDK.java @@ -44,6 +44,7 @@ import im.actor.sdk.core.AndroidNotifications; import im.actor.sdk.core.AndroidPhoneBook; import im.actor.sdk.core.ActorPushManager; +import im.actor.sdk.core.AndroidRawUpdateHandlerProvider; import im.actor.sdk.intents.ActorIntent; import im.actor.sdk.intents.ActorIntentActivity; import im.actor.sdk.intents.ActorIntentFragmentActivity; @@ -298,6 +299,11 @@ public void createActor(final Application application) { // builder.setCallsProvider(new AndroidCallProvider()); + // + // Handle raw updates + // + builder.setRawUpdatesHandlerProvider(new AndroidRawUpdateHandlerProvider()); + // // Auto Join // diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorSDKDelegate.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorSDKDelegate.java index 781e6fc217..de357a4a52 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorSDKDelegate.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorSDKDelegate.java @@ -6,6 +6,7 @@ import org.jetbrains.annotations.Nullable; +import im.actor.core.RawUpdatesHandler; import im.actor.core.entity.Peer; import im.actor.runtime.android.view.BindedViewHolder; import im.actor.sdk.controllers.conversation.ChatFragment; @@ -228,5 +229,10 @@ public interface ActorSDKDelegate { */ int getNotificationColor(); - + /** + * If not null returned, overrides raw updates handler actor + * + * @return RawUpdatesHandler actor + */ + RawUpdatesHandler getRawUpdatesHandler(); } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/BaseActorSDKDelegate.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/BaseActorSDKDelegate.java index eb6e8396f8..9542d2e2c3 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/BaseActorSDKDelegate.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/BaseActorSDKDelegate.java @@ -7,6 +7,7 @@ import org.jetbrains.annotations.Nullable; +import im.actor.core.RawUpdatesHandler; import im.actor.core.entity.Peer; import im.actor.runtime.android.view.BindedViewHolder; import im.actor.sdk.controllers.conversation.ChatFragment; @@ -163,4 +164,9 @@ public Uri getNotificationSound() { public int getNotificationColor() { return ActorSDK.sharedActor().style.getMainColor(); } + + @Override + public RawUpdatesHandler getRawUpdatesHandler() { + return null; + } } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/core/AndroidRawUpdateHandlerProvider.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/core/AndroidRawUpdateHandlerProvider.java new file mode 100644 index 0000000000..357894229b --- /dev/null +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/core/AndroidRawUpdateHandlerProvider.java @@ -0,0 +1,12 @@ +package im.actor.sdk.core; + +import im.actor.core.RawUpdatesHandler; +import im.actor.core.providers.RawUpdatesHandlerProvider; +import im.actor.sdk.ActorSDK; + +public class AndroidRawUpdateHandlerProvider implements RawUpdatesHandlerProvider { + @Override + public RawUpdatesHandler getRawUpdatesHandler() { + return ActorSDK.sharedActor().getDelegate().getRawUpdatesHandler(); + } +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Configuration.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Configuration.java index eb14a49ee3..5d910f8519 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Configuration.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Configuration.java @@ -10,6 +10,7 @@ import im.actor.core.providers.NotificationProvider; import im.actor.core.providers.PhoneBookProvider; import im.actor.core.providers.CallsProvider; +import im.actor.core.providers.RawUpdatesHandlerProvider; import im.actor.runtime.mtproto.ConnectionEndpoint; import im.actor.runtime.webrtc.WebRTCIceServer; @@ -59,6 +60,8 @@ public class Configuration { @Property("readonly, nonatomic") private final CallsProvider callsProvider; @Property("readonly, nonatomic") + private final RawUpdatesHandlerProvider rawUpdatesHandlerProvider; + @Property("readonly, nonatomic") private final boolean isEnabledGroupedChatList; @Property("readonly, nonatomic") private final String[] autoJoinGroups; @@ -83,6 +86,7 @@ public class Configuration { TrustedKey[] trustedKeys, boolean enablePhoneBookImport, CallsProvider callsProvider, + RawUpdatesHandlerProvider rawUpdatesHandlerProvider, boolean voiceCallsEnabled, boolean videoCallsEnabled, boolean isEnabledGroupedChatList, @@ -106,6 +110,7 @@ public class Configuration { this.trustedKeys = trustedKeys; this.enablePhoneBookImport = enablePhoneBookImport; this.callsProvider = callsProvider; + this.rawUpdatesHandlerProvider = rawUpdatesHandlerProvider; this.voiceCallsEnabled = voiceCallsEnabled; this.videoCallsEnabled = videoCallsEnabled; this.isEnabledGroupedChatList = isEnabledGroupedChatList; @@ -140,6 +145,15 @@ public CallsProvider getCallsProvider() { return callsProvider; } + /** + * Getting RawUpdatesHandler provider if set + * + * @return RawUpdatesHandler provider + */ + public RawUpdatesHandlerProvider getRawUpdatesHandlerProvider() { + return rawUpdatesHandlerProvider; + } + /** * Getting if app automatically imports phone book to server * diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/ConfigurationBuilder.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/ConfigurationBuilder.java index 870c88492e..c53df93adb 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/ConfigurationBuilder.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/ConfigurationBuilder.java @@ -15,6 +15,7 @@ import im.actor.core.providers.NotificationProvider; import im.actor.core.providers.PhoneBookProvider; import im.actor.core.providers.CallsProvider; +import im.actor.core.providers.RawUpdatesHandlerProvider; import im.actor.core.util.StringMatch; import im.actor.runtime.Crypto; import im.actor.runtime.Log; @@ -59,6 +60,7 @@ public class ConfigurationBuilder { private boolean isPhoneBookImportEnabled = true; private CallsProvider callsProvider; + private RawUpdatesHandlerProvider rawUpdatesHandlerProvider; private boolean isEnabledGroupedChatList = true; @@ -140,6 +142,19 @@ public ConfigurationBuilder setCallsProvider(CallsProvider callsProvider) { return this; } + /** + * Setting raw updates handler provider + * + * @param rawUpdatesHandlerProvider raw updates handler provider + * @return this + */ + @NotNull + @ObjectiveCName("setRawUpdatesHandlerProvider:") + public ConfigurationBuilder setRawUpdatesHandlerProvider(RawUpdatesHandlerProvider rawUpdatesHandlerProvider) { + this.rawUpdatesHandlerProvider = rawUpdatesHandlerProvider; + return this; + } + /** * Adding Trusted key for protocol encryption securing * @@ -446,6 +461,7 @@ public Configuration build() { trustedKeys.toArray(new TrustedKey[trustedKeys.size()]), isPhoneBookImportEnabled, callsProvider, + rawUpdatesHandlerProvider, voiceCallsEnabled, videoCallsEnabled, isEnabledGroupedChatList, diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/RawUpdatesHandler.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/RawUpdatesHandler.java new file mode 100644 index 0000000000..cfc5c9f4e0 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/RawUpdatesHandler.java @@ -0,0 +1,18 @@ +package im.actor.core; + +import im.actor.core.api.updates.UpdateRawUpdate; +import im.actor.runtime.actors.Actor; + +public abstract class RawUpdatesHandler extends Actor { + + protected abstract void onRawUpdate(UpdateRawUpdate update); + + @Override + public void onReceive(Object message) { + if (message instanceof UpdateRawUpdate) { + onRawUpdate((UpdateRawUpdate) message); + } else { + drop(message); + } + } +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/raw/RawProcessor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/raw/RawProcessor.java new file mode 100644 index 0000000000..577cfb4b19 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/raw/RawProcessor.java @@ -0,0 +1,48 @@ +package im.actor.core.modules.raw; + +import im.actor.core.Configuration; +import im.actor.core.RawUpdatesHandler; +import im.actor.core.api.updates.UpdateRawUpdate; +import im.actor.core.modules.AbsModule; +import im.actor.core.modules.ModuleContext; +import im.actor.core.modules.sequence.processor.SequenceProcessor; +import im.actor.core.modules.sequence.processor.UpdateProcessor; +import im.actor.core.network.parser.Update; +import im.actor.runtime.Log; +import im.actor.runtime.actors.ActorRef; +import im.actor.runtime.actors.ActorSystem; +import im.actor.runtime.actors.Props; +import im.actor.runtime.actors.messages.*; +import im.actor.runtime.actors.messages.Void; +import im.actor.runtime.promise.Promise; +import im.actor.sdk.core.audio.AudioPlayerActor; + +public class RawProcessor extends AbsModule implements SequenceProcessor { + private static final String TAG = "RawProcessor"; + private final ActorRef rawUpdatesHandlerActor; + + public RawProcessor(ModuleContext context) { + super(context); + RawUpdatesHandler rawUpdatesHandler = context().getConfiguration().getRawUpdatesHandlerProvider().getRawUpdatesHandler(); + if (rawUpdatesHandler != null) { + rawUpdatesHandlerActor = ActorSystem.system().actorOf(Props.create(() -> rawUpdatesHandler), "actor/raw_updates"); + } else { + rawUpdatesHandlerActor = ActorSystem.system().actorOf(Props.create(() -> new RawUpdatesHandler() { + @Override + protected void onRawUpdate(UpdateRawUpdate update) { + Log.d(TAG, "update: " + update.toString()); + } + }), "actor/raw_updates"); + + } + } + + @Override + public Promise process(Update update) { + if (update instanceof UpdateRawUpdate) { + rawUpdatesHandlerActor.send(update); + return Promise.success(null); + } + return null; + } +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/sequence/processor/UpdateProcessor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/sequence/processor/UpdateProcessor.java index 90a9637000..3c95412bc3 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/sequence/processor/UpdateProcessor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/sequence/processor/UpdateProcessor.java @@ -23,6 +23,7 @@ import im.actor.core.modules.eventbus.EventBusProcessor; import im.actor.core.modules.groups.GroupsProcessor; import im.actor.core.modules.presence.PresenceProcessor; +import im.actor.core.modules.raw.RawProcessor; import im.actor.core.modules.settings.SettingsProcessor; import im.actor.core.modules.stickers.StickersProcessor; import im.actor.core.modules.typing.TypingProcessor; @@ -65,7 +66,8 @@ public UpdateProcessor(ModuleContext context) { new ContactsProcessor(context), new EncryptedProcessor(context), new StickersProcessor(context), - new SettingsProcessor(context) + new SettingsProcessor(context), + new RawProcessor(context) }; this.typingProcessor = new TypingProcessor(context); diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/providers/RawUpdatesHandlerProvider.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/providers/RawUpdatesHandlerProvider.java new file mode 100644 index 0000000000..bc17a95b91 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/providers/RawUpdatesHandlerProvider.java @@ -0,0 +1,8 @@ +package im.actor.core.providers; + +import im.actor.core.RawUpdatesHandler; + +public interface RawUpdatesHandlerProvider { + + RawUpdatesHandler getRawUpdatesHandler(); +} From 0e4c2d18be93d680bfa5295526328632a2532b2d Mon Sep 17 00:00:00 2001 From: kor_ka Date: Mon, 15 Aug 2016 20:29:42 +0300 Subject: [PATCH 281/414] feat(android): add missed group permissions administration --- .../group/GroupPermissionsFragment.java | 51 +++++++- .../res/layout/fragment_edit_permissions.xml | 112 ++++++++++++++++++ .../src/main/res/values/ui_text.xml | 11 +- .../actor/core/entity/GroupPermissions.java | 56 +++++++++ 4 files changed, 228 insertions(+), 2 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupPermissionsFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupPermissionsFragment.java index 25a23ad0fa..5583cf9ea1 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupPermissionsFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupPermissionsFragment.java @@ -10,6 +10,7 @@ import android.view.View; import android.view.ViewGroup; import android.widget.CheckBox; +import android.widget.TextView; import fr.castorflex.android.circularprogressbar.CircularProgressBar; import im.actor.core.entity.GroupPermissions; @@ -34,8 +35,24 @@ public static GroupPermissionsFragment create(int chatId) { private CircularProgressBar progress; private View scrollContainer; + + private TextView canEditInfoTV; private CheckBox canEditInfo; + private TextView canAdminsEditInfoTV; + private CheckBox canAdminsEditInfo; + + private TextView canSendInvintationsTV; + private CheckBox canSendInvintations; + + private TextView showLeaveJoinTV; + private CheckBox showLeaveJoin; + + private TextView showAdminsToMembersTV; + private CheckBox showAdminsToMembers; + + boolean isChannel = false; + public GroupPermissionsFragment() { setRootFragment(true); setHomeAsUp(true); @@ -48,6 +65,7 @@ public void onCreate(Bundle saveInstance) { groupId = getArguments().getInt("groupId"); if (messenger().getGroup(groupId).getGroupType() == GroupType.CHANNEL) { setTitle(R.string.channel_admin_title); + isChannel = true; } else { setTitle(R.string.group_admin_title); } @@ -59,7 +77,27 @@ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, View res = inflater.inflate(R.layout.fragment_edit_permissions, container, false); View rootContainer = res.findViewById(R.id.rootContainer); rootContainer.setBackgroundColor(style.getBackyardBackgroundColor()); + canEditInfo = (CheckBox) res.findViewById(R.id.canEditValue); + canEditInfoTV = (TextView) res.findViewById(R.id.canEditTitle); + canEditInfoTV.setText(isChannel ? R.string.channel_can_edit_info_members : R.string.group_can_edit_info_members); + + canAdminsEditInfo = (CheckBox) res.findViewById(R.id.canAdminsEditValue); + canAdminsEditInfoTV = (TextView) res.findViewById(R.id.canAdminsEditTitle); + canAdminsEditInfoTV.setText(isChannel ? R.string.channel_can_edit_info_admins : R.string.group_can_edit_info_admins); + + canSendInvintations = (CheckBox) res.findViewById(R.id.canMembersInviteValue); + canSendInvintationsTV = (TextView) res.findViewById(R.id.canMembersInviteTitle); + canSendInvintationsTV.setText(isChannel ? R.string.group_can_invite_members : R.string.channel_can_invite_members); + + showLeaveJoin = (CheckBox) res.findViewById(R.id.showJoinLeaveValue); + showLeaveJoinTV = (TextView) res.findViewById(R.id.showJoinLeaveTitle); + showLeaveJoinTV.setText(isChannel ? R.string.channel_show_leave_join : R.string.group_show_leave_join); + + showAdminsToMembers = (CheckBox) res.findViewById(R.id.showAdminsToMembersValue); + showAdminsToMembersTV = (TextView) res.findViewById(R.id.showAdminsToMembersTitle); + showAdminsToMembersTV.setText(isChannel ? R.string.channel_show_admin_to_members : R.string.group_show_admin_to_members); + scrollContainer = res.findViewById(R.id.scrollContainer); progress = (CircularProgressBar) res.findViewById(R.id.progress); progress.setIndeterminate(true); @@ -90,6 +128,10 @@ public void bindView() { activity.invalidateOptionsMenu(); } canEditInfo.setChecked(permissions.isMembersCanEditInfo()); + canAdminsEditInfo.setChecked(permissions.isAdminsCanEditGroupInfo()); + canSendInvintations.setChecked(permissions.isMembersCanInvite()); + showLeaveJoin.setChecked(permissions.isShowJoinLeaveMessages()); + showAdminsToMembers.setChecked(permissions.isShowAdminsToMembers()); } @Override @@ -103,8 +145,15 @@ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { @Override public boolean onOptionsItemSelected(MenuItem item) { if (item.getItemId() == R.id.next) { - if (permissions.isMembersCanEditInfo() != canEditInfo.isChecked()) { + if (permissions.isMembersCanEditInfo() != canEditInfo.isChecked() || + permissions.isAdminsCanEditGroupInfo() != canAdminsEditInfo.isChecked() || + permissions.isMembersCanInvite() != canSendInvintations.isChecked() || + permissions.isShowJoinLeaveMessages() != showLeaveJoin.isChecked() || + permissions.isShowAdminsToMembers() != showAdminsToMembers.isChecked()) { permissions.setMembersCanEditInfo(canEditInfo.isChecked()); + permissions.setAdminsCanEditGroupInfo(canAdminsEditInfo.isChecked()); + permissions.setShowJoinLeaveMessages(showLeaveJoin.isChecked()); + permissions.setShowAdminsToMembers(showAdminsToMembers.isChecked()); execute(messenger().saveGroupPermissions(groupId, permissions).then(r -> { finishActivity(); })); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_edit_permissions.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_edit_permissions.xml index 07cb8a6378..6e8b2d9ca8 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_edit_permissions.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_edit_permissions.xml @@ -50,6 +50,118 @@ android:textSize="16sp" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml index 83ed6abe90..3a2ebf87e5 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml @@ -393,7 +393,16 @@ All members will see all messages All members can edit group info - All members can edit group info + Admins can edit group info + All members can invite to group + Show member leave/join messages + Admin label is visible to all members + + All members can edit channel info + Admins can edit channel info + All members can invite to channel + Show member leave/join messages + Admin label is visible to all members Public Group Public groups can be found in search, anyone can join them. diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/GroupPermissions.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/GroupPermissions.java index cc0b5fff66..6b06015165 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/GroupPermissions.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/GroupPermissions.java @@ -13,6 +13,43 @@ public GroupPermissions(ApiAdminSettings settings) { this.settings = settings; } + + @ObjectiveCName("isShowAdminsToMembers") + public boolean isShowAdminsToMembers() { + return settings.showAdminsToMembers(); + } + + @ObjectiveCName("showAdminsToMembers:") + public void setShowAdminsToMembers(boolean showAdminsToMembers) { + SparseArray unmapped = settings.getUnmappedObjects(); + settings = new ApiAdminSettings( + showAdminsToMembers, + settings.canMembersInvite(), + settings.canMembersEditGroupInfo(), + settings.canAdminsEditGroupInfo(), + settings.showJoinLeaveMessages() + ); + settings.setUnmappedObjects(unmapped); + } + + @ObjectiveCName("isMembersCanInvite") + public boolean isMembersCanInvite() { + return settings.canMembersInvite(); + } + + @ObjectiveCName("setMembersCanInvite:") + public void setMembersCanInvite(boolean membersCanInvite) { + SparseArray unmapped = settings.getUnmappedObjects(); + settings = new ApiAdminSettings( + settings.showAdminsToMembers(), + membersCanInvite, + settings.canMembersEditGroupInfo(), + settings.canAdminsEditGroupInfo(), + settings.showJoinLeaveMessages() + ); + settings.setUnmappedObjects(unmapped); + } + @ObjectiveCName("isMembersCanEditInfo") public boolean isMembersCanEditInfo() { return settings.canMembersEditGroupInfo(); @@ -31,6 +68,25 @@ public void setMembersCanEditInfo(boolean canEditInfo) { settings.setUnmappedObjects(unmapped); } + @ObjectiveCName("isAdminsCanEditGroupInfo") + public boolean isAdminsCanEditGroupInfo() { + return settings.canAdminsEditGroupInfo(); + } + + @ObjectiveCName("setAdminsCanEditGroupInfo:") + public void setAdminsCanEditGroupInfo(boolean adminsCanEditGroupInfo) { + SparseArray unmapped = settings.getUnmappedObjects(); + settings = new ApiAdminSettings( + settings.showAdminsToMembers(), + settings.canMembersInvite(), + settings.canMembersEditGroupInfo(), + adminsCanEditGroupInfo, + settings.showJoinLeaveMessages() + ); + settings.setUnmappedObjects(unmapped); + } + + @ObjectiveCName("isShowJoinLeaveMessages") public boolean isShowJoinLeaveMessages() { return settings.showJoinLeaveMessages(); From 06687a47c8915e6cf3535211f9cfa096999c4fe8 Mon Sep 17 00:00:00 2001 From: yumazaki Date: Tue, 16 Aug 2016 14:10:01 +0800 Subject: [PATCH 282/414] Update Localizable.strings Improve the Chinese translation --- .../zh-Hans.lproj/Localizable.strings | 186 ++++++++++-------- 1 file changed, 102 insertions(+), 84 deletions(-) diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/zh-Hans.lproj/Localizable.strings b/actor-sdk/sdk-core-ios/ActorSDK/Resources/zh-Hans.lproj/Localizable.strings index f01e50f481..b2f2714c52 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Resources/zh-Hans.lproj/Localizable.strings +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/zh-Hans.lproj/Localizable.strings @@ -34,7 +34,7 @@ "DialogsHint" = "向左滑动查看更多选项"; -"DialogsBack" = "Chats"; +"DialogsBack" = "消息"; /* * Settings @@ -48,6 +48,13 @@ "SettingsChangeName" = "修改姓名"; +"SettingsEditHeader" = "你的姓名"; + +"SettingsEditHint" = "请输入你的真实姓名以便别人更好的认识你。"; + +"SettingsEditFieldHint" = "请输入你的姓名"; + + "SettingsUsernameNotSet" = "设置用户名"; "SettingsUsernameTitle" = "用户名"; @@ -64,7 +71,7 @@ "SettingsChangeAboutHint" = "简单介绍一下你自己"; -"SettingsSecurity" = "Privacy and Security"; +"SettingsSecurity" = "隐私和安全"; "SettingsWallpapers" = "聊天背景"; @@ -82,9 +89,9 @@ "SettingsMobilePhone" = "手机"; -"SettingsGroupChats" = "Groups"; +"SettingsGroupChats" = "群组"; -"SettingsPrivateChats" = "Private Chats"; +"SettingsPrivateChats" = "私密聊天"; /* * Notifications @@ -122,6 +129,20 @@ "NotificationsOnlyMentionsHint" = "只有当消息中提到你时才通知"; +/* + * Media + */ + +"MediaTitle" = "媒体设置"; + +"MediaPhotoDownloadHeader"= "自动下载照片"; + +"MediaAudioDownloadHeader"= "自动下载语音"; + +"MediaOtherHeader"= "其他"; + +"MediaAutoplayGif"= "自动播放GIFs图片"; + /* * Privacy */ @@ -129,22 +150,22 @@ "PrivacyTitle" = "安全"; -"PrivacyHeader" = "Privacy"; +"PrivacyHeader" = "隐私"; "PrivacyLastSeen" = "最后一次露面"; "PrivacyLastSeenHint" = "更改谁可以看到您最后一次露面时间。"; -"SettingsLastSeenEverybody" = "每个人都可见"; +"PrivacyLastSeenEverybody" = "每个人都可见"; -"SettingsLastSeenContacts" = "联系人可见"; +"PrivacyLastSeenContacts" = "联系人可见"; -"SettingsLastSeenNone" = "没人"; +"PrivacyLastSeenNone" = "没人"; -"PrivacySecurityHeader" = "Security"; +"PrivacySecurityHeader" = "安全"; -"PrivacyAllSessions" = "Active Sessions"; +"PrivacyAllSessions" = "在线的设备"; "PrivacyTerminate" = "下线所有其它登录"; @@ -194,6 +215,7 @@ "CallGroupText" = "{name}发起群组通话"; + /* * Profile */ @@ -212,11 +234,15 @@ "ProfileRemoveFromContacts" = "从通讯录中删除"; +"ProfileBlockContact" = "黑名单"; + +"ProfileUnblockContact" = "开启用户"; + "ProfileRename" = "修改联系名"; "ProfileRenameMessage" = "你可以修改通讯录中的联系人名称,此修改只体现在你的通讯录中。"; -"ProfileRenameAction" = ""; +"ProfileRenameAction" = "修改名称"; "ProfileAbout" = "自我介绍"; @@ -226,22 +252,12 @@ "ProfileTitle" = "详细资料"; -"ProfileBlockContact" = "Block User"; - -"ProfileUnblockContact" = "Unblock User"; - "CallNumber" = "拨打电话"; "CopyNumber" = "复制号码"; "NumberCopied" = "电话号码已经复制到剪贴板中"; -"SettingsEditHeader" = "姓名"; - -"SettingsEditHint" = "请输入您的真实姓名,以便于好友识别。"; - -"SettingsEditFieldHint" = "请输入姓名"; - /* * Publics */ @@ -260,6 +276,8 @@ "GroupSetTitle" = "设置群组名称"; +"GroupSetSound" = "设置群组音效"; + "GroupEditConfirm" = "确定要修改群组名称吗?"; "GroupEditConfirmAction" = "修改"; @@ -270,80 +288,82 @@ "GroupIntegrations" = "集成"; -"GroupViewMembers" = "个成员"; +"GroupViewMembers" = "成员"; -"GroupMemberAdmin" = "管理员"; +"GroupDescription" = "简介"; -"GroupMemberInfo" = "成员信息"; -"GroupDescription" = "Description"; +"GroupAdministration" = "管理员"; +"GroupTypeTitle" = "群组类型"; -"GroupAdministration" = "Administration"; +"GroupTypeTitleChannel" = "频道类型"; -"GroupTypeTitle" = "Group Type"; +"GroupPermissionsHint" = "设置群组类型"; -"GroupTypeTitleChannel" = "Channel Type"; +"GroupPermissionsHintChannel" = "设置频道类型"; -"ChannelTypePublic" = "Public"; +"GroupTypeHintPublic" = "公开群组可以被搜索并且任何人可以主动加入"; -"ChannelTypePrivate" = "Private"; +"GroupTypeHintPrivate" = "私密群组只有创建者可以邀请其他人加入"; -"GroupTypePublicFull" = "Public Group"; +"GroupTypeHintPublicChannel" = "公开频道可以被搜索并且任何人可以主动加入"; -"GroupTypePrivateFull" = "Private Group"; +"GroupTypeHintPrivateChannel" = "私密频道只有创建者可以邀请其他人加入"; -"ChannelTypePublicFull" = "Public Channel"; +"GroupTypeLinkHint" = "群员可以共享此链接给其他人加入群组"; -"ChannelTypePrivateFull" = "Private Channel"; +"GroupTypeLinkHintChannel" = "群员可以共享此链接给其他人加入频道"; -"GroupPermissionsHint" = "Control what is possible in this group"; +"GroupTypePublic" = "公开的"; -"GroupPermissionsHintChannel" = "Control what is possible in this channel"; +"GroupTypePrivate" = "私密的"; -"GroupTypeHintPublic" = "Public groups can be found in search and anyone can join"; +"ChannelTypePublic" = "公开的"; -"GroupTypeHintPrivate" = "Private groups can be joined only via personal invitation"; +"ChannelTypePrivate" = "私密的"; -"GroupTypeHintPublicChannel" = "Public channels can be found in search and anyone can join"; +"GroupTypePublicFull" = "公开群组"; -"GroupTypeHintPrivateChannel" = "Private channels can be joined only via personal invitation"; +"GroupTypePrivateFull" = "私密群组"; -"GroupTypeLinkHint" = "People can share this link with others and find your group using search"; +"ChannelTypePublicFull" = "公开频道"; -"GroupTypeLinkHintChannel" = "People can share this link with others and find your channel using search"; +"ChannelTypePrivateFull" = "私密频道"; -"GroupDeleteHint" = "You will lose all messages in this group"; +"GroupDeleteHint" = "你将丢失所有此群组的聊天记录"; -"GroupDeleteHintChannel" = "You will lose all messages in this channel"; +"GroupDeleteHintChannel" = "你将丢失所有此频道的消息记录"; +"GroupShareTitle" = "公开历史记录"; -"GroupShareTitle" = "Shared History"; +"GroupShareEnabled" = "公开"; -"GroupShareEnabled" = "Shared"; +"GroupShareHint" = "所有成员将可以查看此群组的所有历史聊天记录"; -"GroupShareHint" = "All members will see all messages"; +"GroupShareMessage" = "你想公开所有历史聊天记录给所有成员吗?此操作不可逆。"; -"GroupShareMessage" = "Are you sure want to share all messages to all members? This action is irreversible."; +"GroupShareAction" = "公开"; -"GroupShareAction" = "Share"; +"GroupEditTitle" = "编辑群组"; -"GroupEditTitle" = "Edit Group"; +"GroupEditTitleChannel" = "编辑频道"; -"GroupEditTitleChannel" = "Edit Channel"; +"GroupEditName" = "群组名称"; -"GroupEditName" = "Group Name"; +"GroupEditNameChannel" = "频道名称"; -"GroupEditNameChannel" = "Channel Name"; +"GroupEditDescription" = "简介"; -"GroupEditDescription" = "Description"; +"GroupMemberAdmin" = "管理员"; +"GroupMemberInfo" = "配置"; "GroupMemberMakeAdmin" = "设为群组管理员"; @@ -370,7 +390,6 @@ "GroupAddParticipantUrl" = "通过URL链接邀请加入群组"; - "GroupInviteLinkPageTitle" = "邀请链接"; "GroupInviteLinkTitle" = "链接"; @@ -396,7 +415,7 @@ "CreateGroup" = "创建群组"; -"CreateChannel" = "Create Channel"; +"CreateChannel" = "创建频道"; "CreateGroupTitle" = "创建群组"; @@ -405,11 +424,11 @@ "CreateGroupNamePlaceholder" = "输入群组名称"; -"CreateChannelTitle" = "Create Channel"; +"CreateChannelTitle" = "创建频道"; -"CreateChannelHint" = "Please provide the channel name and an optional channel icon"; +"CreateChannelHint" = "请输入频道名称并设置频道图标"; -"CreateChannelNamePlaceholder" = "Channel Name"; +"CreateChannelNamePlaceholder" = "频道名称"; "CreateGroupMembersTitle" = "邀请成员"; @@ -444,9 +463,9 @@ "Chat.MicrophoneAccessDisabled" = "我们需要访问麦克风来发送语音信息. 请打开 设置 — 隐私 — 麦克风,然后打开开关"; -"ChatDeleted" = "This chat was deleted by owner."; +"ChatDeleted" = "此聊天已被发起者删除"; -"ChatJoin" = "Join"; +"ChatJoin" = "加入"; /* * Chat attachment menu @@ -542,12 +561,12 @@ "AuthPhoneTitle" = "请输入你的手机号码"; +"AuthPhoneUseEmail" = "使用邮件"; + "AuthPhoneHint" = "为了保障你的个人信息安全,需要验证你的手机号码。"; "AuthPhonePlaceholder" = "手机号码"; -"AuthPhoneUseEmail" = "使用邮件"; - "AuthEmailTitle" = "请输入你的电子邮件"; "AuthEmailHint" = "不会向你发送任何垃圾邮件。"; @@ -580,6 +599,7 @@ "AuthOTPCallHint" = "{app_name}将在{time}秒后拨打手机告知验证码"; + /* * Common Elements */ @@ -624,6 +644,8 @@ * Alerts */ +"AlertUnblock" = "取消黑名单"; + "AlertCancel" = "取消"; "AlertNext" = "下一步"; @@ -646,7 +668,6 @@ "UnsupportedContent" = "这个版本不支持此消息。请等待应用升级,之后就可以看到消息了。"; - /* * Actions */ @@ -669,48 +690,46 @@ "ActionOpenCode" = "查看代码"; -"ActionMute" = "Mute"; - -"ActionUnmute" = "Unmute"; +"ActionMute" = "禁言"; +"ActionUnmute" = "取消禁言"; -"ActionDelete" = "Delete"; +"ActionDelete" = "删除"; -"ActionDeleteMessage" = "Are you sure want to delete chat?"; +"ActionDeleteMessage" = "确定删除聊天吗?"; -"ActionDeleteChannel" = "Delete Channel"; +"ActionDeleteChannel" = "删除频道"; -"ActionDeleteChannelMessage" = "Wait! Deleting this channel will remove all members and all messages will be lost. Delete the channel anyway?"; +"ActionDeleteChannelMessage" = "删除此频道将清空成员和消息记录,确定要这么做吗?"; -"ActionDeleteGroup" = "Delete Group"; +"ActionDeleteGroup" = "删除群组"; -"ActionDeleteGroupMessage" = "Wait! Deleting this group will remove all members and all messages will be lost. Delete the group anyway?"; +"ActionDeleteGroupMessage" = "删除此群组将清空成员和消息记录,确定要这么做吗?"; -"ActionLeaveChannel" = "Leave Channel"; +"ActionLeaveChannel" = "离开频道"; -"ActionLeaveChannelMessage" = "Are you sure want to leave channel?"; +"ActionLeaveChannelMessage" = "确定离开频道吗?"; -"ActionLeaveChannelAction" = "Leave"; +"ActionLeaveChannelAction" = "离开"; -"ActionDeleteAndExit" = "Delete and Exit"; +"ActionDeleteAndExit" = "删除并退出"; -"ActionDeleteAndExitMessage" = "Are you sure want to exit group and delete all messages?"; +"ActionDeleteAndExitMessage" = "确定退出群组并删除消息吗?"; -"ActionDeleteAndExitAction" = "Exit"; +"ActionDeleteAndExitAction" = "退出"; -"ActionClearHistory" = "Clear History"; +"ActionClearHistory" = "清除历史"; -"ActionClearHistoryMessage" = "Are you sure want to clear history?"; - -"ActionClearHistoryAction" = "Clear"; +"ActionClearHistoryMessage" = "确定清除所有历史吗?"; +"ActionClearHistoryAction" = "清除"; /* * Network @@ -733,4 +752,3 @@ "ErrorUnableToJoin" = "无法加入群组"; "ErrorUnableToCall" = "无法拨打这个号码"; - From 32fc6df27950e5896681bde8b9aed0b0b6bf2b8c Mon Sep 17 00:00:00 2001 From: kor_ka Date: Tue, 16 Aug 2016 14:45:10 +0300 Subject: [PATCH 283/414] chore(android): group permissions ru translation --- .../controllers/group/GroupAdminFragment.java | 1 + .../group/GroupPermissionsFragment.java | 24 +++++++++++-------- .../src/main/res/values-ru/ui_text.xml | 13 +++++++++- 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupAdminFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupAdminFragment.java index d27dc58400..ef2a32fc54 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupAdminFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupAdminFragment.java @@ -128,6 +128,7 @@ public void onChanged(String val, Value valueModel) { } else { permissions.setVisibility(View.GONE); permissionsDiv.setVisibility(View.GONE); + permissionsHint.setVisibility(View.GONE); } // Group Deletion diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupPermissionsFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupPermissionsFragment.java index 5583cf9ea1..9e93f2ed9e 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupPermissionsFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupPermissionsFragment.java @@ -42,8 +42,8 @@ public static GroupPermissionsFragment create(int chatId) { private TextView canAdminsEditInfoTV; private CheckBox canAdminsEditInfo; - private TextView canSendInvintationsTV; - private CheckBox canSendInvintations; + private TextView canSendInvitationsTV; + private CheckBox canSendInvitations; private TextView showLeaveJoinTV; private CheckBox showLeaveJoin; @@ -86,13 +86,17 @@ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, canAdminsEditInfoTV = (TextView) res.findViewById(R.id.canAdminsEditTitle); canAdminsEditInfoTV.setText(isChannel ? R.string.channel_can_edit_info_admins : R.string.group_can_edit_info_admins); - canSendInvintations = (CheckBox) res.findViewById(R.id.canMembersInviteValue); - canSendInvintationsTV = (TextView) res.findViewById(R.id.canMembersInviteTitle); - canSendInvintationsTV.setText(isChannel ? R.string.group_can_invite_members : R.string.channel_can_invite_members); + canSendInvitations = (CheckBox) res.findViewById(R.id.canMembersInviteValue); + canSendInvitationsTV = (TextView) res.findViewById(R.id.canMembersInviteTitle); + canSendInvitationsTV.setText(isChannel ? R.string.group_can_invite_members : R.string.channel_can_invite_members); - showLeaveJoin = (CheckBox) res.findViewById(R.id.showJoinLeaveValue); - showLeaveJoinTV = (TextView) res.findViewById(R.id.showJoinLeaveTitle); - showLeaveJoinTV.setText(isChannel ? R.string.channel_show_leave_join : R.string.group_show_leave_join); + if (!isChannel) { + showLeaveJoin = (CheckBox) res.findViewById(R.id.showJoinLeaveValue); + showLeaveJoinTV = (TextView) res.findViewById(R.id.showJoinLeaveTitle); + showLeaveJoinTV.setText(isChannel ? R.string.channel_show_leave_join : R.string.group_show_leave_join); + } else { + res.findViewById(R.id.showJoinLeaveContainer).setVisibility(View.GONE); + } showAdminsToMembers = (CheckBox) res.findViewById(R.id.showAdminsToMembersValue); showAdminsToMembersTV = (TextView) res.findViewById(R.id.showAdminsToMembersTitle); @@ -129,7 +133,7 @@ public void bindView() { } canEditInfo.setChecked(permissions.isMembersCanEditInfo()); canAdminsEditInfo.setChecked(permissions.isAdminsCanEditGroupInfo()); - canSendInvintations.setChecked(permissions.isMembersCanInvite()); + canSendInvitations.setChecked(permissions.isMembersCanInvite()); showLeaveJoin.setChecked(permissions.isShowJoinLeaveMessages()); showAdminsToMembers.setChecked(permissions.isShowAdminsToMembers()); } @@ -147,7 +151,7 @@ public boolean onOptionsItemSelected(MenuItem item) { if (item.getItemId() == R.id.next) { if (permissions.isMembersCanEditInfo() != canEditInfo.isChecked() || permissions.isAdminsCanEditGroupInfo() != canAdminsEditInfo.isChecked() || - permissions.isMembersCanInvite() != canSendInvintations.isChecked() || + permissions.isMembersCanInvite() != canSendInvitations.isChecked() || permissions.isShowJoinLeaveMessages() != showLeaveJoin.isChecked() || permissions.isShowAdminsToMembers() != showAdminsToMembers.isChecked()) { permissions.setMembersCanEditInfo(canEditInfo.isChecked()); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-ru/ui_text.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-ru/ui_text.xml index 8f4b3238ec..a3f8253ec9 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-ru/ui_text.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-ru/ui_text.xml @@ -383,7 +383,6 @@ Администрирование Назначить администратором Аннулировать права администратора - Все пользователи могут редактировать информацию о группе Удалить группу Вы потеряете все сообщения в этой группе Описание @@ -411,7 +410,19 @@ Администрирование канала + + Все пользователи могут редактировать информацию о группе + Администраторы могут редактировать информацию о группе + Все пользователи могут приглашать в группу + Показывать сообщения о входе/выходе из группы + Показывать информацию о том, кто является администратором пользователям + Все пользователи могут редактировать иформацию о канале + Администраторы могут редактировать информацию о канале + Все пользователи могут приглашать в канал + Показывать сообщения о входе/выходе из канала + Показывать информацию о том, кто является администратором пользователям + Удалить канал Вы потеряете все сообщения в этом канале Управление разрешениями в этом канале From 698e5183c0e4b9e5f842efbb995d91f2a212d851 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Tue, 16 Aug 2016 17:11:01 +0300 Subject: [PATCH 284/414] fix(core): run raw update on Runtime.dispatch() --- .../src/main/java/im/actor/sdk/ActorSDK.java | 3 +- .../core/AndroidRawUpdateHandlerProvider.java | 12 ------ .../java/im/actor/core/Configuration.java | 16 +++---- .../im/actor/core/ConfigurationBuilder.java | 15 +++---- .../java/im/actor/core/RawUpdatesHandler.java | 14 ++---- .../actor/core/modules/raw/RawProcessor.java | 43 +++++++++---------- .../providers/RawUpdatesHandlerProvider.java | 8 ---- 7 files changed, 39 insertions(+), 72 deletions(-) delete mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/core/AndroidRawUpdateHandlerProvider.java delete mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/providers/RawUpdatesHandlerProvider.java diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorSDK.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorSDK.java index 723cfd1057..9c19e605e9 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorSDK.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorSDK.java @@ -44,7 +44,6 @@ import im.actor.sdk.core.AndroidNotifications; import im.actor.sdk.core.AndroidPhoneBook; import im.actor.sdk.core.ActorPushManager; -import im.actor.sdk.core.AndroidRawUpdateHandlerProvider; import im.actor.sdk.intents.ActorIntent; import im.actor.sdk.intents.ActorIntentActivity; import im.actor.sdk.intents.ActorIntentFragmentActivity; @@ -302,7 +301,7 @@ public void createActor(final Application application) { // // Handle raw updates // - builder.setRawUpdatesHandlerProvider(new AndroidRawUpdateHandlerProvider()); + builder.setRawUpdatesHandler(getDelegate().getRawUpdatesHandler()); // // Auto Join diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/core/AndroidRawUpdateHandlerProvider.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/core/AndroidRawUpdateHandlerProvider.java deleted file mode 100644 index 357894229b..0000000000 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/core/AndroidRawUpdateHandlerProvider.java +++ /dev/null @@ -1,12 +0,0 @@ -package im.actor.sdk.core; - -import im.actor.core.RawUpdatesHandler; -import im.actor.core.providers.RawUpdatesHandlerProvider; -import im.actor.sdk.ActorSDK; - -public class AndroidRawUpdateHandlerProvider implements RawUpdatesHandlerProvider { - @Override - public RawUpdatesHandler getRawUpdatesHandler() { - return ActorSDK.sharedActor().getDelegate().getRawUpdatesHandler(); - } -} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Configuration.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Configuration.java index 5d910f8519..665496feaa 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Configuration.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Configuration.java @@ -10,9 +10,7 @@ import im.actor.core.providers.NotificationProvider; import im.actor.core.providers.PhoneBookProvider; import im.actor.core.providers.CallsProvider; -import im.actor.core.providers.RawUpdatesHandlerProvider; import im.actor.runtime.mtproto.ConnectionEndpoint; -import im.actor.runtime.webrtc.WebRTCIceServer; /** * Configuration for Messenger @@ -60,7 +58,7 @@ public class Configuration { @Property("readonly, nonatomic") private final CallsProvider callsProvider; @Property("readonly, nonatomic") - private final RawUpdatesHandlerProvider rawUpdatesHandlerProvider; + private final RawUpdatesHandler rawUpdatesHandler; @Property("readonly, nonatomic") private final boolean isEnabledGroupedChatList; @Property("readonly, nonatomic") @@ -86,7 +84,7 @@ public class Configuration { TrustedKey[] trustedKeys, boolean enablePhoneBookImport, CallsProvider callsProvider, - RawUpdatesHandlerProvider rawUpdatesHandlerProvider, + RawUpdatesHandler rawUpdatesHandler, boolean voiceCallsEnabled, boolean videoCallsEnabled, boolean isEnabledGroupedChatList, @@ -110,7 +108,7 @@ public class Configuration { this.trustedKeys = trustedKeys; this.enablePhoneBookImport = enablePhoneBookImport; this.callsProvider = callsProvider; - this.rawUpdatesHandlerProvider = rawUpdatesHandlerProvider; + this.rawUpdatesHandler = rawUpdatesHandler; this.voiceCallsEnabled = voiceCallsEnabled; this.videoCallsEnabled = videoCallsEnabled; this.isEnabledGroupedChatList = isEnabledGroupedChatList; @@ -146,12 +144,12 @@ public CallsProvider getCallsProvider() { } /** - * Getting RawUpdatesHandler provider if set + * Getting RawUpdatesHandler if set * - * @return RawUpdatesHandler provider + * @return raw updates handler */ - public RawUpdatesHandlerProvider getRawUpdatesHandlerProvider() { - return rawUpdatesHandlerProvider; + public RawUpdatesHandler getRawUpdatesHandler() { + return rawUpdatesHandler; } /** diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/ConfigurationBuilder.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/ConfigurationBuilder.java index c53df93adb..c7e061b345 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/ConfigurationBuilder.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/ConfigurationBuilder.java @@ -15,7 +15,6 @@ import im.actor.core.providers.NotificationProvider; import im.actor.core.providers.PhoneBookProvider; import im.actor.core.providers.CallsProvider; -import im.actor.core.providers.RawUpdatesHandlerProvider; import im.actor.core.util.StringMatch; import im.actor.runtime.Crypto; import im.actor.runtime.Log; @@ -60,7 +59,7 @@ public class ConfigurationBuilder { private boolean isPhoneBookImportEnabled = true; private CallsProvider callsProvider; - private RawUpdatesHandlerProvider rawUpdatesHandlerProvider; + private RawUpdatesHandler rawUpdatesHandler; private boolean isEnabledGroupedChatList = true; @@ -143,15 +142,15 @@ public ConfigurationBuilder setCallsProvider(CallsProvider callsProvider) { } /** - * Setting raw updates handler provider + * Setting raw updates handler * - * @param rawUpdatesHandlerProvider raw updates handler provider + * @param rawUpdatesHandler raw updates handler * @return this */ @NotNull - @ObjectiveCName("setRawUpdatesHandlerProvider:") - public ConfigurationBuilder setRawUpdatesHandlerProvider(RawUpdatesHandlerProvider rawUpdatesHandlerProvider) { - this.rawUpdatesHandlerProvider = rawUpdatesHandlerProvider; + @ObjectiveCName("setRawUpdatesHandler:") + public ConfigurationBuilder setRawUpdatesHandler(RawUpdatesHandler rawUpdatesHandler) { + this.rawUpdatesHandler = rawUpdatesHandler; return this; } @@ -461,7 +460,7 @@ public Configuration build() { trustedKeys.toArray(new TrustedKey[trustedKeys.size()]), isPhoneBookImportEnabled, callsProvider, - rawUpdatesHandlerProvider, + rawUpdatesHandler, voiceCallsEnabled, videoCallsEnabled, isEnabledGroupedChatList, diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/RawUpdatesHandler.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/RawUpdatesHandler.java index cfc5c9f4e0..923487ba8b 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/RawUpdatesHandler.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/RawUpdatesHandler.java @@ -2,17 +2,11 @@ import im.actor.core.api.updates.UpdateRawUpdate; import im.actor.runtime.actors.Actor; +import im.actor.runtime.actors.messages.Void; +import im.actor.runtime.promise.Promise; -public abstract class RawUpdatesHandler extends Actor { +public abstract class RawUpdatesHandler { - protected abstract void onRawUpdate(UpdateRawUpdate update); + public abstract Promise onRawUpdate(UpdateRawUpdate update); - @Override - public void onReceive(Object message) { - if (message instanceof UpdateRawUpdate) { - onRawUpdate((UpdateRawUpdate) message); - } else { - drop(message); - } - } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/raw/RawProcessor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/raw/RawProcessor.java index 577cfb4b19..5d9e95a322 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/raw/RawProcessor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/raw/RawProcessor.java @@ -1,47 +1,44 @@ package im.actor.core.modules.raw; -import im.actor.core.Configuration; + import im.actor.core.RawUpdatesHandler; import im.actor.core.api.updates.UpdateRawUpdate; import im.actor.core.modules.AbsModule; import im.actor.core.modules.ModuleContext; import im.actor.core.modules.sequence.processor.SequenceProcessor; -import im.actor.core.modules.sequence.processor.UpdateProcessor; import im.actor.core.network.parser.Update; import im.actor.runtime.Log; -import im.actor.runtime.actors.ActorRef; -import im.actor.runtime.actors.ActorSystem; -import im.actor.runtime.actors.Props; -import im.actor.runtime.actors.messages.*; import im.actor.runtime.actors.messages.Void; import im.actor.runtime.promise.Promise; -import im.actor.sdk.core.audio.AudioPlayerActor; +import im.actor.runtime.Runtime; public class RawProcessor extends AbsModule implements SequenceProcessor { - private static final String TAG = "RawProcessor"; - private final ActorRef rawUpdatesHandlerActor; + + private final RawUpdatesHandler rawUpdatesHandler; public RawProcessor(ModuleContext context) { super(context); - RawUpdatesHandler rawUpdatesHandler = context().getConfiguration().getRawUpdatesHandlerProvider().getRawUpdatesHandler(); - if (rawUpdatesHandler != null) { - rawUpdatesHandlerActor = ActorSystem.system().actorOf(Props.create(() -> rawUpdatesHandler), "actor/raw_updates"); - } else { - rawUpdatesHandlerActor = ActorSystem.system().actorOf(Props.create(() -> new RawUpdatesHandler() { - @Override - protected void onRawUpdate(UpdateRawUpdate update) { - Log.d(TAG, "update: " + update.toString()); - } - }), "actor/raw_updates"); + rawUpdatesHandler = context().getConfiguration().getRawUpdatesHandler(); - } } @Override public Promise process(Update update) { - if (update instanceof UpdateRawUpdate) { - rawUpdatesHandlerActor.send(update); - return Promise.success(null); + if (update instanceof UpdateRawUpdate && rawUpdatesHandler != null) { + return new Promise<>(resolver -> { + Runtime.dispatch(() -> { + try { + Promise promise = rawUpdatesHandler.onRawUpdate((UpdateRawUpdate) update); + if (promise != null) { + promise.pipeTo(resolver); + } + } catch (Exception e) { + Log.e("RawUpdateHandler", e); + } finally { + resolver.result(null); + } + }); + }); } return null; } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/providers/RawUpdatesHandlerProvider.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/providers/RawUpdatesHandlerProvider.java deleted file mode 100644 index bc17a95b91..0000000000 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/providers/RawUpdatesHandlerProvider.java +++ /dev/null @@ -1,8 +0,0 @@ -package im.actor.core.providers; - -import im.actor.core.RawUpdatesHandler; - -public interface RawUpdatesHandlerProvider { - - RawUpdatesHandler getRawUpdatesHandler(); -} From 86e6b6f79e02de6a5766a10dcb4747e4c446700f Mon Sep 17 00:00:00 2001 From: kor_ka Date: Tue, 16 Aug 2016 17:49:06 +0300 Subject: [PATCH 285/414] fix(android): fix permissions ru translation --- .../actor/sdk/controllers/group/GroupPermissionsFragment.java | 2 +- .../android-sdk/src/main/res/values-ru/ui_text.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupPermissionsFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupPermissionsFragment.java index 9e93f2ed9e..3a51d0dbb7 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupPermissionsFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupPermissionsFragment.java @@ -88,7 +88,7 @@ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, canSendInvitations = (CheckBox) res.findViewById(R.id.canMembersInviteValue); canSendInvitationsTV = (TextView) res.findViewById(R.id.canMembersInviteTitle); - canSendInvitationsTV.setText(isChannel ? R.string.group_can_invite_members : R.string.channel_can_invite_members); + canSendInvitationsTV.setText(isChannel ? R.string.channel_can_invite_members : R.string.group_can_invite_members); if (!isChannel) { showLeaveJoin = (CheckBox) res.findViewById(R.id.showJoinLeaveValue); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-ru/ui_text.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-ru/ui_text.xml index a3f8253ec9..23b47736a2 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-ru/ui_text.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-ru/ui_text.xml @@ -410,7 +410,7 @@ Администрирование канала - + w Все пользователи могут редактировать информацию о группе Администраторы могут редактировать информацию о группе Все пользователи могут приглашать в группу From d62fcc466186b110675877b03e3969770f3b882b Mon Sep 17 00:00:00 2001 From: rockjam Date: Tue, 16 Aug 2016 22:24:20 +0300 Subject: [PATCH 286/414] fix(server): don't push update members on admin set --- .../server/group/AdminCommandHandlers.scala | 47 +++---------------- 1 file changed, 7 insertions(+), 40 deletions(-) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/AdminCommandHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/AdminCommandHandlers.scala index ba92089378..cc568ef846 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/AdminCommandHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/AdminCommandHandlers.scala @@ -68,7 +68,7 @@ private[group] trait AdminCommandHandlers extends GroupsImplicits { val members = newState.members.values.map(_.asStruct).toVector val updateAdmin = UpdateGroupMemberAdminChanged(groupId, cmd.candidateUserId, isAdmin = true) - val updateMembers = UpdateGroupMembersUpdated(groupId, members) + val updateMembers = UpdateGroupMembersUpdated(groupId, members) // don't push it! // now this user is admin, change edit rules for admins val updatePermissions = permissionsUpdates(cmd.candidateUserId, newState) @@ -149,54 +149,17 @@ private[group] trait AdminCommandHandlers extends GroupsImplicits { persist(AdminStatusChanged(Instant.now, cmd.targetUserId, isAdmin = false)) { evt ⇒ val newState = commit(evt) - val dateMillis = evt.ts.toEpochMilli val memberIds = newState.memberIds val members = newState.members.values.map(_.asStruct).toVector val updateAdmin = UpdateGroupMemberAdminChanged(groupId, cmd.targetUserId, isAdmin = false) - val updateMembers = UpdateGroupMembersUpdated(groupId, members) - // now this user is not admin, change edit rules to plain members - val updatePermissions = permissionsUpdates(cmd.targetUserId, newState) - // val updateCanEdit = UpdateGroupCanEditInfoChanged(groupId, canEditGroup = newState.adminSettings.canMembersEditGroupInfo) val updateObsolete = UpdateGroupMembersUpdateObsolete(groupId, members) //TODO: remove deprecated db.run(GroupUserRepo.dismissAdmin(groupId, cmd.targetUserId): @silent) - val adminGROUPUpdates: Future[SeqState] = - for { - // push admin changed to all - _ ← seqUpdExt.broadcastPeopleUpdate( - userIds = memberIds + cmd.clientUserId, - updateAdmin - ) - // push changed members to all users - seqState ← seqUpdExt.broadcastClientUpdate( - cmd.clientUserId, - cmd.clientAuthId, - memberIds - cmd.clientUserId, - updateMembers - ) - } yield seqState - - val adminCHANNELUpdates: Future[SeqState] = - for { - // push admin changed to all - _ ← seqUpdExt.broadcastPeopleUpdate( - userIds = memberIds + cmd.clientUserId, - updateAdmin - ) - // push changed members to admins and fresh admin - seqState ← seqUpdExt.broadcastClientUpdate( - cmd.clientUserId, - cmd.clientAuthId, - newState.adminIds - cmd.clientUserId, - updateMembers - ) - } yield seqState - val result: Future[SeqState] = for { /////////////////////////// @@ -216,8 +179,12 @@ private[group] trait AdminCommandHandlers extends GroupsImplicits { _ ← FutureExt.ftraverse(updatePermissions) { update ⇒ seqUpdExt.deliverUserUpdate(cmd.targetUserId, update) } - seqState ← if (state.groupType.isChannel) adminCHANNELUpdates else adminGROUPUpdates - + seqState ← seqUpdExt.broadcastClientUpdate( + cmd.clientUserId, + cmd.clientAuthId, + memberIds - cmd.clientUserId, + updateAdmin + ) } yield seqState result pipeTo sender() From 26a0faee757d526f2915f3e5f59867baf373876f Mon Sep 17 00:00:00 2001 From: kor_ka Date: Wed, 17 Aug 2016 14:52:08 +0300 Subject: [PATCH 287/414] fix(android): video calls local video aspect ratio --- .../sdk/controllers/calls/CallFragment.java | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/calls/CallFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/calls/CallFragment.java index 2fdeb61e3b..ca797701a9 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/calls/CallFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/calls/CallFragment.java @@ -309,7 +309,32 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, remoteVideoView = (SurfaceViewRenderer) cont.findViewById(R.id.remote_renderer); - localVideoView = new SurfaceViewRenderer(getActivity()); + localVideoView = new SurfaceViewRenderer(getActivity()) { + private boolean aspectFixed = false; + + @Override + public void renderFrame(VideoRenderer.I420Frame frame) { + if (!aspectFixed) { + aspectFixed = true; + int maxWH = Screen.getWidth() / 3 - Screen.dp(20); + float scale = Math.min(maxWH / (float) frame.width, maxWH / (float) frame.height); + + int destW = (int) (scale * frame.width); + int destH = (int) (scale * frame.height); + + boolean turned = frame.rotationDegree % 90 % 2 == 0; + + localVideoView.post(new Runnable() { + @Override + public void run() { + localVideoView.getLayoutParams().height = turned ? destW : destH; + localVideoView.getLayoutParams().width = turned ? destH : destW; + } + }); + } + super.renderFrame(frame); + } + }; localVideoView.setVisibility(View.INVISIBLE); localVideoView.setZOrderMediaOverlay(true); From cc902e68bd98e06e5d0735dd8ebedf3acdeb8372 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Wed, 17 Aug 2016 16:59:09 +0300 Subject: [PATCH 288/414] feat(core + android): change endpoints on auth --- .../src/main/java/im/actor/sdk/ActorSDK.java | 23 +++++++ .../controllers/auth/BaseAuthFragment.java | 42 +++++++++++- .../sdk/controllers/auth/SignInFragment.java | 4 ++ .../sdk/controllers/auth/SignUpFragment.java | 5 ++ .../android-sdk/src/main/res/menu/sign_in.xml | 5 ++ .../android-sdk/src/main/res/menu/sign_up.xml | 5 ++ .../src/main/res/values-ru/ui_text.xml | 4 ++ .../src/main/res/values/ui_text.xml | 3 + .../im/actor/core/ConfigurationBuilder.java | 52 ++------------ .../main/java/im/actor/core/Messenger.java | 15 +++++ .../im/actor/core/modules/api/ApiModule.java | 46 ++++++++++++- .../core/modules/auth/Authentication.java | 4 ++ .../core/modules/storage/StorageModule.java | 5 ++ .../java/im/actor/core/network/ActorApi.java | 20 +++++- .../java/im/actor/core/network/Endpoints.java | 47 ++++++++++++- .../im/actor/core/network/TrustedKey.java | 44 +++++++++++- .../im/actor/core/network/api/ApiBroker.java | 32 ++++++++- .../runtime/mtproto/ConnectionEndpoint.java | 48 ++++++++++++- .../mtproto/ConnectionEndpointArray.java | 67 +++++++++++++++++++ 19 files changed, 414 insertions(+), 57 deletions(-) create mode 100644 actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/mtproto/ConnectionEndpointArray.java diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorSDK.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorSDK.java index 9c19e605e9..35f21e58ac 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorSDK.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorSDK.java @@ -182,6 +182,11 @@ public class ActorSDK { */ private int authType = AuthActivity.AUTH_TYPE_PHONE + AuthActivity.AUTH_TYPE_EMAIL; + /** + * Alternate endpoints - allow choose alternate endpoint on auth - disabled be default + */ + private boolean useAlternateEndpoints = false; + /** * Delegate */ @@ -679,6 +684,24 @@ public void setCallsEnabled(boolean callsEnabled) { this.callsEnabled = callsEnabled; } + /** + * Alternate endpoints - allow choose alternate endpoint on auth - disabled be default + * + * @return is isUseAlternateEndpointsEnabled enabled + */ + public boolean isUseAlternateEndpointsEnabled() { + return useAlternateEndpoints; + } + + /** + * Is alternate endpoints choose enabled + * + * @param useAlternateEndpoints is setUseAlternateEndpoints enabled + */ + public void setUseAlternateEndpoints(boolean useAlternateEndpoints) { + this.useAlternateEndpoints = useAlternateEndpoints; + } + /** * Is calls enabled. * diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/auth/BaseAuthFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/auth/BaseAuthFragment.java index ba9b162934..def1393158 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/auth/BaseAuthFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/auth/BaseAuthFragment.java @@ -12,19 +12,26 @@ import android.text.method.LinkMovementMethod; import android.text.style.ClickableSpan; import android.util.Patterns; +import android.view.Menu; +import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; +import android.view.ViewGroup; import android.widget.EditText; +import android.widget.FrameLayout; import android.widget.TextView; +import android.widget.Toast; import java.util.regex.Pattern; import im.actor.core.entity.AuthRes; import im.actor.core.entity.Sex; +import im.actor.runtime.mtproto.ConnectionEndpointArray; import im.actor.runtime.promise.Promise; import im.actor.sdk.ActorSDK; import im.actor.sdk.R; import im.actor.sdk.controllers.BaseFragment; +import im.actor.sdk.util.Screen; import im.actor.sdk.view.BaseUrlSpan; import im.actor.sdk.view.CustomClicableSpan; @@ -233,7 +240,6 @@ public void onClick(DialogInterface dialog, int which) { builder.setSpan(span, index, index + ppIndex.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE); } - @Override public boolean onOptionsItemSelected(MenuItem item) { int i = item.getItemId(); @@ -242,6 +248,40 @@ public boolean onOptionsItemSelected(MenuItem item) { return true; } else if (i == R.id.sign_up) { startSignUp(); + return true; + } else if (i == R.id.change_endpoint) { + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + builder.setTitle(R.string.auth_change_endpoint); + + final EditText input = new EditText(getActivity()); + input.setText("tcp://"); + input.setSelection(input.getText().length()); + + int padding = Screen.dp(25); + FrameLayout inputContainer = new FrameLayout(getActivity()); + FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); + params.setMargins(padding, padding, padding, 0); + inputContainer.addView(input, params); + builder.setView(inputContainer); + + builder.setPositiveButton(R.string.dialog_ok, (dialog, which) -> { + try { + messenger().changeEndpoint(input.getText().toString()); + } catch (ConnectionEndpointArray.UnknownSchemeException e) { + Toast.makeText(getActivity(), e.getMessage(), Toast.LENGTH_LONG).show(); + } + }); + builder.setNegativeButton(R.string.auth_reset_default_endpoint, (dialog, which) -> { + try { + messenger().changeEndpoint(null); + } catch (ConnectionEndpointArray.UnknownSchemeException e) { + e.printStackTrace(); + } + }); + + builder.show(); + input.requestFocus(); + return true; } else { return super.onOptionsItemSelected(item); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/auth/SignInFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/auth/SignInFragment.java index ab6c5bc790..bef363fbc0 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/auth/SignInFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/auth/SignInFragment.java @@ -181,6 +181,10 @@ public void onPrepareOptionsMenu(Menu menu) { super.onPrepareOptionsMenu(menu); menu.clear(); getActivity().getMenuInflater().inflate(R.menu.sign_in, menu); + MenuItem item = menu.findItem(R.id.change_endpoint); + if (item != null) { + item.setVisible(ActorSDK.sharedActor().isUseAlternateEndpointsEnabled()); + } } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/auth/SignUpFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/auth/SignUpFragment.java index db0f8c53f1..58342c54e4 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/auth/SignUpFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/auth/SignUpFragment.java @@ -8,6 +8,7 @@ import android.text.TextWatcher; import android.view.LayoutInflater; import android.view.Menu; +import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.EditText; @@ -98,5 +99,9 @@ public void onPrepareOptionsMenu(Menu menu) { super.onPrepareOptionsMenu(menu); menu.clear(); getActivity().getMenuInflater().inflate(R.menu.sign_up, menu); + MenuItem item = menu.findItem(R.id.change_endpoint); + if (item != null) { + item.setVisible(ActorSDK.sharedActor().isUseAlternateEndpointsEnabled()); + } } } \ No newline at end of file diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/menu/sign_in.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/menu/sign_in.xml index 20ba1e8aa5..dc7c72f8f6 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/menu/sign_in.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/menu/sign_in.xml @@ -12,4 +12,9 @@ android:title="@string/tour_sign_up" app:showAsAction="always" /> + + \ No newline at end of file diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/menu/sign_up.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/menu/sign_up.xml index f14405c8f6..154df3d205 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/menu/sign_up.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/menu/sign_up.xml @@ -12,4 +12,9 @@ android:title="@string/tour_sign_in" app:showAsAction="always" /> + + \ No newline at end of file diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-ru/ui_text.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-ru/ui_text.xml index 23b47736a2..77841633d7 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-ru/ui_text.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-ru/ui_text.xml @@ -81,6 +81,10 @@ Политика Конфиденциальности Условия Использования + Использовать альтернативный сервер + По умолчанию + + Выбор способа авторизации Пожалуйста, выберите способ авторизации. diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml index 3a2ebf87e5..c1566bfc22 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml @@ -106,6 +106,9 @@ By signing up, you agree to the Privacy Policy. Others users will be able to find you via email or phone number. + Change server endpoint + reset to default + Terms of Service Privacy Policy diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/ConfigurationBuilder.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/ConfigurationBuilder.java index c7e061b345..3942b86857 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/ConfigurationBuilder.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/ConfigurationBuilder.java @@ -19,6 +19,7 @@ import im.actor.runtime.Crypto; import im.actor.runtime.Log; import im.actor.runtime.mtproto.ConnectionEndpoint; +import im.actor.runtime.mtproto.ConnectionEndpointArray; import im.actor.runtime.util.Hex; import im.actor.runtime.webrtc.WebRTCIceServer; @@ -28,7 +29,7 @@ public class ConfigurationBuilder { private ArrayList trustedKeys = new ArrayList<>(); - private ArrayList endpoints = new ArrayList<>(); + private ConnectionEndpointArray endpoints = new ConnectionEndpointArray(); private PhoneBookProvider phoneBookProvider; @@ -375,51 +376,10 @@ public ConfigurationBuilder setMaxFailureCount(int maxFailureCount) { @NotNull @ObjectiveCName("addEndpoint:") public ConfigurationBuilder addEndpoint(@NotNull String url) { - - // Manual buggy parsing for GWT - // TODO: Correct URL parsing - String scheme = url.substring(0, url.indexOf(":")).toLowerCase(); - String host = url.substring(url.indexOf("://") + "://".length()); - String knownIp = null; - - if (host.endsWith("/")) { - host = host.substring(0, host.length() - 1); - } - int port = -1; - if (host.contains(":")) { - String[] parts = host.split(":"); - host = parts[0]; - port = Integer.parseInt(parts[1]); - } - - if (host.contains("@")) { - String[] parts = host.split("@"); - host = parts[0]; - knownIp = parts[1]; - } - - if (scheme.equals("ssl") || scheme.equals("tls")) { - if (port <= 0) { - port = 443; - } - endpoints.add(new ConnectionEndpoint(host, port, knownIp, ConnectionEndpoint.TYPE_TCP_TLS)); - } else if (scheme.equals("tcp")) { - if (port <= 0) { - port = 80; - } - endpoints.add(new ConnectionEndpoint(host, port, knownIp, ConnectionEndpoint.TYPE_TCP)); - } else if (scheme.equals("ws")) { - if (port <= 0) { - port = 80; - } - endpoints.add(new ConnectionEndpoint(host, port, knownIp, ConnectionEndpoint.TYPE_WS)); - } else if (scheme.equals("wss")) { - if (port <= 0) { - port = 443; - } - endpoints.add(new ConnectionEndpoint(host, port, knownIp, ConnectionEndpoint.TYPE_WS_TLS)); - } else { - throw new RuntimeException("Unknown scheme type: " + scheme); + try { + endpoints.addEndpoint(url); + } catch (ConnectionEndpointArray.UnknownSchemeException e) { + throw new RuntimeException(e.getMessage()); } return this; } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java index 8755054bb5..f9937c9452 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java @@ -70,6 +70,7 @@ import im.actor.core.viewmodel.UserVM; import im.actor.runtime.actors.ActorSystem; import im.actor.runtime.actors.messages.Void; +import im.actor.runtime.mtproto.ConnectionEndpointArray; import im.actor.runtime.mvvm.MVVMCollection; import im.actor.runtime.mvvm.SearchValueModel; import im.actor.runtime.mvvm.ValueModel; @@ -223,6 +224,20 @@ public Promise doCompleteAuth(AuthRes authRes) { return modules.getAuthModule().doCompleteAuth(authRes); } + /** + * Change endpoint + * + * @param endpoint endpoint to change to, null for reset to default + * @throws ConnectionEndpointArray.UnknownSchemeException + */ + public void changeEndpoint(String endpoint) throws ConnectionEndpointArray.UnknownSchemeException { + if (endpoint != null && !endpoint.isEmpty()) { + modules.getApiModule().changeEndpoint(endpoint); + } else { + modules.getApiModule().resetToDefaultEndpoints(); + } + } + /** * Request email auth * diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/api/ApiModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/api/ApiModule.java index 6fd1b13d4f..c233afe035 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/api/ApiModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/api/ApiModule.java @@ -1,5 +1,7 @@ package im.actor.core.modules.api; +import java.io.IOException; + import im.actor.core.modules.AbsModule; import im.actor.core.modules.Modules; import im.actor.core.events.AppVisibleChanged; @@ -9,10 +11,14 @@ import im.actor.core.network.ActorApiCallback; import im.actor.core.network.AuthKeyStorage; import im.actor.core.network.Endpoints; +import im.actor.core.network.TrustedKey; +import im.actor.core.network.api.ApiBroker; import im.actor.core.network.parser.Request; import im.actor.runtime.actors.ActorRef; import im.actor.runtime.eventbus.BusSubscriber; import im.actor.runtime.eventbus.Event; +import im.actor.runtime.mtproto.ConnectionEndpoint; +import im.actor.runtime.mtproto.ConnectionEndpointArray; import static im.actor.runtime.actors.ActorSystem.system; @@ -27,8 +33,21 @@ public ApiModule(Modules context) { this.authKeyStorage = new PreferenceApiStorage(context().getPreferences()); - this.actorApi = new ActorApi(new Endpoints(context().getConfiguration().getEndpoints(), - context().getConfiguration().getTrustedKeys()), + Endpoints endpoints = null; + byte[] customEndpointsBytes = context().getPreferences().getBytes("custom_endpoints"); + if (customEndpointsBytes != null) { + try { + endpoints = Endpoints.fromBytes(customEndpointsBytes); + } catch (IOException e) { + e.printStackTrace(); + } + } + + if (endpoints == null) { + endpoints = new Endpoints(context().getConfiguration().getEndpoints(), context().getConfiguration().getTrustedKeys()); + } + + this.actorApi = new ActorApi(endpoints, authKeyStorage, new ActorApiCallbackImpl(), context().getConfiguration().isEnableNetworkLogging(), @@ -82,6 +101,29 @@ public void performPersistCursorRequest(String name, long key, Request request) persistentRequests.send(new PersistentRequestsActor.PerformCursorRequest(name, key, request)); } + /** + * Changing endpoint + */ + public void changeEndpoint(String endpoint) throws ConnectionEndpointArray.UnknownSchemeException { + changeEndpoints(new Endpoints(new ConnectionEndpointArray().addEndpoint(endpoint).toArray(new ConnectionEndpoint[1]), new TrustedKey[0])); + } + + /** + * Changing endpoints + */ + public synchronized void changeEndpoints(Endpoints endpoints) { + context().getPreferences().putBytes("custom_endpoints", endpoints.toByteArray()); + actorApi.changeEndpoints(endpoints); + } + + /** + * Reset default endpoints + */ + public synchronized void resetToDefaultEndpoints() { + context().getPreferences().putBytes("custom_endpoints", null); + actorApi.resetToDefaultEndpoints(); + } + @Override public void onBusEvent(Event event) { if (event instanceof AppVisibleChanged) { diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/auth/Authentication.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/auth/Authentication.java index 70e8e1590a..9a06531fe8 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/auth/Authentication.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/auth/Authentication.java @@ -36,14 +36,18 @@ import im.actor.core.entity.User; import im.actor.core.modules.Modules; import im.actor.core.modules.AbsModule; +import im.actor.core.network.Endpoints; import im.actor.core.network.RpcCallback; import im.actor.core.network.RpcException; +import im.actor.core.network.TrustedKey; import im.actor.core.network.parser.Request; import im.actor.core.network.parser.Response; import im.actor.core.viewmodel.Command; import im.actor.core.viewmodel.CommandCallback; import im.actor.runtime.*; import im.actor.runtime.Runtime; +import im.actor.runtime.mtproto.ConnectionEndpoint; +import im.actor.runtime.mtproto.ConnectionEndpointArray; import im.actor.runtime.promise.Promise; import im.actor.runtime.promise.PromiseFunc; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/storage/StorageModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/storage/StorageModule.java index 40ede34306..79ec3ee010 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/storage/StorageModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/storage/StorageModule.java @@ -45,6 +45,7 @@ private void performUpgrade(boolean isFirst) { AuthKeyStorage storage = context().getActorApi().getKeyStorage(); long authKey = storage.getAuthKey(); byte[] masterKey = storage.getAuthMasterKey(); + byte[] endpoints = preferences().getBytes("custom_endpoints"); AuthenticationBackupData authenticationBackupData = null; if (!isFirst) { authenticationBackupData = context().getAuthModule().performBackup(); @@ -73,5 +74,9 @@ private void performUpgrade(boolean isFirst) { if (authenticationBackupData != null) { context().getAuthModule().restoreBackup(authenticationBackupData); } + + if (endpoints != null) { + preferences().putBytes("custom_endpoints", endpoints); + } } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/network/ActorApi.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/network/ActorApi.java index 04d082c294..a5401af5fa 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/network/ActorApi.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/network/ActorApi.java @@ -25,7 +25,8 @@ public class ActorApi { private static final AtomicIntegerCompat NEXT_ID = im.actor.runtime.Runtime.createAtomicInt(1); private static final AtomicLongCompat NEXT_RPC_ID = im.actor.runtime.Runtime.createAtomicLong(1); - private final Endpoints endpoints; + private Endpoints endpoints; + private Endpoints defaultEndpoints; private final AuthKeyStorage keyStorage; private final ActorApiCallback callback; private final boolean isEnableLog; @@ -47,6 +48,7 @@ public ActorApi(Endpoints endpoints, AuthKeyStorage keyStorage, ActorApiCallback int maxDelay, int maxFailureCount) { this.endpoints = endpoints; + this.defaultEndpoints = endpoints; this.keyStorage = keyStorage; this.callback = callback; this.isEnableLog = isEnableLog; @@ -113,6 +115,22 @@ public synchronized void forceNetworkCheck() { this.apiBroker.send(new ApiBroker.ForceNetworkCheck()); } + /** + * Changing endpoints + */ + public synchronized void changeEndpoints(Endpoints endpoints) { + this.endpoints = endpoints; + this.apiBroker.send(new ApiBroker.ChangeEndpoints(endpoints)); + } + + /** + * Reset default endpoints + */ + public synchronized void resetToDefaultEndpoints() { + this.endpoints = defaultEndpoints; + this.apiBroker.send(new ApiBroker.ChangeEndpoints(endpoints)); + } + public AuthKeyStorage getKeyStorage() { return keyStorage; } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/network/Endpoints.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/network/Endpoints.java index 8548985441..9d2b086c8b 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/network/Endpoints.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/network/Endpoints.java @@ -4,16 +4,26 @@ package im.actor.core.network; +import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import im.actor.runtime.bser.Bser; +import im.actor.runtime.bser.BserObject; +import im.actor.runtime.bser.BserValues; +import im.actor.runtime.bser.BserWriter; import im.actor.runtime.mtproto.ConnectionEndpoint; -public class Endpoints { +public class Endpoints extends BserObject { private int roundRobin = 0; private ConnectionEndpoint[] endpoints; private TrustedKey[] trustedKeys; + public Endpoints() { + } + public Endpoints(ConnectionEndpoint[] endpoints, TrustedKey[] trustedKeys) { this.endpoints = endpoints; this.trustedKeys = trustedKeys; @@ -55,4 +65,39 @@ public ConnectionEndpoint fetchEndpoint(boolean preferEncrypted) { roundRobin = (roundRobin + 1) % endpoints.length; return endpoints[roundRobin]; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Endpoints endpoints1 = (Endpoints) o; + + return Arrays.equals(endpoints, endpoints1.endpoints) && Arrays.equals(trustedKeys, endpoints1.trustedKeys); + } + + public static Endpoints fromBytes(byte[] data) throws IOException { + return Bser.parse(new Endpoints(), data); + } + + @Override + public void parse(BserValues values) throws IOException { + List endpointsRepeatedBytes = values.getRepeatedBytes(1); + endpoints = new ConnectionEndpoint[endpointsRepeatedBytes.size()]; + for (int i = 0; i < endpoints.length; i++) { + endpoints[i] = ConnectionEndpoint.fromBytes(endpointsRepeatedBytes.get(i)); + } + + List trustedKeysRepeatedBytes = values.getRepeatedBytes(2); + trustedKeys = new TrustedKey[trustedKeysRepeatedBytes.size()]; + for (int i = 0; i < trustedKeys.length; i++) { + trustedKeys[i] = TrustedKey.fromBytes(trustedKeysRepeatedBytes.get(i)); + } + } + + @Override + public void serialize(BserWriter writer) throws IOException { + writer.writeRepeatedObj(1, new ArrayList<>(Arrays.asList(endpoints))); + writer.writeRepeatedObj(2, new ArrayList<>(Arrays.asList(trustedKeys))); + } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/network/TrustedKey.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/network/TrustedKey.java index 5f59451fdd..1251bfda1a 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/network/TrustedKey.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/network/TrustedKey.java @@ -1,6 +1,12 @@ package im.actor.core.network; +import java.io.IOException; + import im.actor.runtime.Crypto; +import im.actor.runtime.bser.Bser; +import im.actor.runtime.bser.BserObject; +import im.actor.runtime.bser.BserValues; +import im.actor.runtime.bser.BserWriter; import im.actor.runtime.crypto.Digest; import im.actor.runtime.crypto.primitives.util.ByteStrings; import im.actor.runtime.util.Hex; @@ -11,13 +17,16 @@ #define J2OBJC_DISABLE_ARRAY_BOUND_CHECKS 1 ]-*/ -public class TrustedKey { +public class TrustedKey extends BserObject { private boolean isLoaded = false; - private final String hexKey; + private String hexKey; private long keyId; private byte[] key; + public TrustedKey() { + } + public TrustedKey(String hexKey) { this.hexKey = hexKey; } @@ -45,4 +54,35 @@ private synchronized void load() { this.keyId = ByteStrings.bytesToLong(hash); } } + + public static TrustedKey fromBytes(byte[] data) throws IOException { + return Bser.parse(new TrustedKey(), data); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + TrustedKey that = (TrustedKey) o; + + return hexKey.equals(that.hexKey); + + } + + @Override + public void parse(BserValues values) throws IOException { + isLoaded = values.getBool(1); + hexKey = values.getString(2); + keyId = values.getLong(3); + key = values.getBytes(4); + } + + @Override + public void serialize(BserWriter writer) throws IOException { + writer.writeBool(1, isLoaded); + writer.writeString(2, hexKey); + writer.writeLong(3, keyId); + writer.writeBytes(4, key); + } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/network/api/ApiBroker.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/network/api/ApiBroker.java index ce3fe2c249..8e491209cd 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/network/api/ApiBroker.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/network/api/ApiBroker.java @@ -55,7 +55,7 @@ public static ActorRef get(final Endpoints endpoints, final AuthKeyStorage keySt private static final AtomicIntegerCompat NEXT_PROTO_ID = im.actor.runtime.Runtime.createAtomicInt(1); - private final Endpoints endpoints; + private Endpoints endpoints; private final AuthKeyStorage keyStorage; private final ActorApiCallback callback; private final boolean isEnableLog; @@ -103,6 +103,14 @@ public void preStart() { } } + public void changeEndpoints(Endpoints endpoints) { + if (endpoints.equals(this.endpoints)) { + return; + } + this.endpoints = endpoints; + recreateAuthId(); + } + @Override public void postStop() { if (proto != null) { @@ -140,13 +148,17 @@ private void onAuthIdInvalidated(long authId) { Log.w(TAG, "Auth id invalidated"); + callback.onAuthIdInvalidated(); + + recreateAuthId(); + } + + private void recreateAuthId() { keyStorage.saveAuthKey(0); keyStorage.saveMasterKey(null); currentAuthId = 0; proto = null; - callback.onAuthIdInvalidated(); - this.keyManager.send(new AuthKeyActor.StartKeyCreation(this.endpoints), self()); } @@ -437,6 +449,18 @@ public static class ForceNetworkCheck { } + public static class ChangeEndpoints { + Endpoints endpoints; + + public ChangeEndpoints(Endpoints endpoints) { + this.endpoints = endpoints; + } + + public Endpoints getEndpoints() { + return endpoints; + } + } + private class InitMTProto { private long authId; private byte[] authKey; @@ -657,6 +681,8 @@ public void onReceive(Object message) { } else if (message instanceof AuthKeyActor.KeyCreated) { onKeyCreated(((AuthKeyActor.KeyCreated) message).getAuthKeyId(), ((AuthKeyActor.KeyCreated) message).getAuthKey()); + } else if (message instanceof ChangeEndpoints) { + changeEndpoints(((ChangeEndpoints) message).getEndpoints()); } else { super.onReceive(message); } diff --git a/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/mtproto/ConnectionEndpoint.java b/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/mtproto/ConnectionEndpoint.java index 8e4fd87b66..44e722a901 100644 --- a/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/mtproto/ConnectionEndpoint.java +++ b/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/mtproto/ConnectionEndpoint.java @@ -10,7 +10,14 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -public class ConnectionEndpoint { +import java.io.IOException; + +import im.actor.runtime.bser.Bser; +import im.actor.runtime.bser.BserObject; +import im.actor.runtime.bser.BserValues; +import im.actor.runtime.bser.BserWriter; + +public class ConnectionEndpoint extends BserObject { public static final int TYPE_TCP = 0; public static final int TYPE_TCP_TLS = 1; @@ -28,6 +35,9 @@ public class ConnectionEndpoint { @Property("readonly, nonatomic") private int type; + public ConnectionEndpoint() { + } + @ObjectiveCName("initWithHost:withPort:withKnownIp:withType:") public ConnectionEndpoint(@NotNull String host, int port, @Nullable String knownIp, int type) { this.host = host; @@ -53,4 +63,40 @@ public int getPort() { public String getKnownIp() { return knownIp; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ConnectionEndpoint that = (ConnectionEndpoint) o; + + if (port != that.port) return false; + if (type != that.type) return false; + if (!host.equals(that.host)) return false; + return !(knownIp != null ? !knownIp.equals(that.knownIp) : that.knownIp != null); + + } + + public static ConnectionEndpoint fromBytes(byte[] data) throws IOException { + return Bser.parse(new ConnectionEndpoint(), data); + } + + @Override + public void parse(BserValues values) throws IOException { + host = values.getString(1); + knownIp = values.optString(2); + port = values.getInt(3); + type = values.getInt(4); + } + + @Override + public void serialize(BserWriter writer) throws IOException { + writer.writeString(1, host); + if (knownIp != null) { + writer.writeString(2, knownIp); + } + writer.writeInt(3, port); + writer.writeInt(4, type); + } } diff --git a/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/mtproto/ConnectionEndpointArray.java b/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/mtproto/ConnectionEndpointArray.java new file mode 100644 index 0000000000..7f99a848c8 --- /dev/null +++ b/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/mtproto/ConnectionEndpointArray.java @@ -0,0 +1,67 @@ +package im.actor.runtime.mtproto; + +import com.google.j2objc.annotations.ObjectiveCName; + +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; + +public class ConnectionEndpointArray extends ArrayList { + @NotNull + @ObjectiveCName("addEndpoint:") + public ConnectionEndpointArray addEndpoint(@NotNull String url) throws UnknownSchemeException { + + // Manual buggy parsing for GWT + // TODO: Correct URL parsing + String scheme = url.substring(0, url.indexOf(":")).toLowerCase(); + String host = url.substring(url.indexOf("://") + "://".length()); + String knownIp = null; + + if (host.endsWith("/")) { + host = host.substring(0, host.length() - 1); + } + int port = -1; + if (host.contains(":")) { + String[] parts = host.split(":"); + host = parts[0]; + port = Integer.parseInt(parts[1]); + } + + if (host.contains("@")) { + String[] parts = host.split("@"); + host = parts[0]; + knownIp = parts[1]; + } + + if (scheme.equals("ssl") || scheme.equals("tls")) { + if (port <= 0) { + port = 443; + } + add(new ConnectionEndpoint(host, port, knownIp, ConnectionEndpoint.TYPE_TCP_TLS)); + } else if (scheme.equals("tcp")) { + if (port <= 0) { + port = 80; + } + add(new ConnectionEndpoint(host, port, knownIp, ConnectionEndpoint.TYPE_TCP)); + } else if (scheme.equals("ws")) { + if (port <= 0) { + port = 80; + } + add(new ConnectionEndpoint(host, port, knownIp, ConnectionEndpoint.TYPE_WS)); + } else if (scheme.equals("wss")) { + if (port <= 0) { + port = 443; + } + add(new ConnectionEndpoint(host, port, knownIp, ConnectionEndpoint.TYPE_WS_TLS)); + } else { + throw new UnknownSchemeException("Unknown scheme type: " + scheme); + } + return this; + } + + public class UnknownSchemeException extends Exception { + public UnknownSchemeException(String message) { + super(message); + } + } +} \ No newline at end of file From 67fccd9af0c951bb165adb54a42c7436b9495054 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Thu, 18 Aug 2016 18:17:55 +0300 Subject: [PATCH 289/414] ref(android): new holders customization --- .../src/main/java/im/actor/Application.java | 8 + .../holders/BubbleTextHolderLayouter.java | 51 ++++ .../im/actor/holders/TextHolderLayouter.java | 48 ++++ .../src/main/java/im/actor/sdk/ActorSDK.java | 34 --- .../java/im/actor/sdk/ActorSDKDelegate.java | 38 +-- .../im/actor/sdk/BaseActorSDKDelegate.java | 21 +- .../conversation/messages/BubbleLayouter.java | 14 ++ .../messages/MessagesAdapter.java | 233 ++++++++---------- .../messages/MessagesFragment.java | 10 +- .../messages/ViewHolderMatcher.java | 48 ++++ .../content/AbsMessageViewHolder.java | 17 ++ .../messages/content/AudioHolder.java | 3 +- .../messages/content/BaseJsonHolder.java | 46 ---- .../messages/content/ContactHolder.java | 3 +- .../messages/content/DocHolder.java | 7 +- .../messages/content/LocationHolder.java | 3 +- .../messages/content/MessageHolder.java | 6 +- .../messages/content/PhotoHolder.java | 3 +- .../messages/content/ServiceHolder.java | 8 +- .../messages/content/StickerHolder.java | 3 +- .../messages/content/TextHolder.java | 3 +- .../messages/content/UnsupportedHolder.java | 5 +- .../java/im/actor/sdk/util/ViewUtils.java | 9 + .../src/main/res/layout/custom_holder.xml | 19 +- .../modules/encryption/KeyManagerActor.java | 4 +- .../im/actor/runtime/crypto/box/ActorBox.java | 2 +- .../im/actor/runtime/markdown/Patterns.java | 4 +- 27 files changed, 360 insertions(+), 290 deletions(-) create mode 100644 actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/holders/BubbleTextHolderLayouter.java create mode 100644 actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/holders/TextHolderLayouter.java create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/BubbleLayouter.java create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/ViewHolderMatcher.java create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/AbsMessageViewHolder.java delete mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/BaseJsonHolder.java diff --git a/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/Application.java b/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/Application.java index 9f7f8be97a..c853f6538f 100644 --- a/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/Application.java +++ b/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/Application.java @@ -12,10 +12,12 @@ import org.jetbrains.annotations.Nullable; +import java.util.ArrayList; import java.util.List; import im.actor.core.entity.Peer; import im.actor.develop.R; +import im.actor.holders.BubbleTextHolderLayouter; import im.actor.sdk.ActorSDK; import im.actor.sdk.ActorSDKApplication; import im.actor.sdk.ActorStyle; @@ -23,6 +25,7 @@ import im.actor.sdk.controllers.conversation.attach.ShareMenuField; import im.actor.sdk.controllers.conversation.attach.AbsAttachFragment; import im.actor.sdk.controllers.conversation.attach.AttachFragment; +import im.actor.sdk.controllers.conversation.messages.BubbleLayouter; import im.actor.sdk.controllers.root.RootFragment; import im.actor.sdk.controllers.settings.ActorSettingsCategories; import im.actor.sdk.controllers.settings.ActorSettingsCategory; @@ -87,6 +90,11 @@ public void onConfigureActorSDK() { private class ActorSDKDelegate extends BaseActorSDKDelegate { + @Override + public void configureChatViewHolders(ArrayList layouters) { + layouters.add(0, new BubbleTextHolderLayouter()); + } + @Nullable @Override public Fragment fragmentForRoot() { diff --git a/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/holders/BubbleTextHolderLayouter.java b/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/holders/BubbleTextHolderLayouter.java new file mode 100644 index 0000000000..5ead5160b8 --- /dev/null +++ b/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/holders/BubbleTextHolderLayouter.java @@ -0,0 +1,51 @@ +package im.actor.holders; + +import android.graphics.Color; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import im.actor.core.entity.Message; +import im.actor.core.entity.Peer; +import im.actor.core.entity.content.AbsContent; +import im.actor.core.entity.content.TextContent; +import im.actor.develop.R; +import im.actor.sdk.controllers.conversation.messages.BubbleLayouter; +import im.actor.sdk.controllers.conversation.messages.MessagesAdapter; +import im.actor.sdk.controllers.conversation.messages.content.MessageHolder; +import im.actor.sdk.controllers.conversation.messages.content.AbsMessageViewHolder; +import im.actor.sdk.controllers.conversation.messages.content.preprocessor.PreprocessedData; +import im.actor.sdk.controllers.conversation.view.BubbleContainer; + +public class BubbleTextHolderLayouter implements BubbleLayouter { + + @Override + public boolean isMatch(AbsContent content) { + return content instanceof TextContent; + } + + @Override + public AbsMessageViewHolder onCreateViewHolder(MessagesAdapter adapter, ViewGroup root, Peer peer) { + TextView itemView = new TextView(root.getContext()); + itemView.setId(R.id.text); + BubbleContainer container = new BubbleContainer(root.getContext()); + container.addView(itemView, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); + return new TextHolderEx(adapter, container); + } + + private class TextHolderEx extends MessageHolder { + TextView tv; + + public TextHolderEx(MessagesAdapter adapter, View itemView) { + super(adapter, itemView, false); + tv = (TextView) container.findViewById(R.id.text); + tv.setTextColor(Color.RED); + } + + @Override + protected void bindData(Message message, long readDate, long receiveDate, boolean isUpdated, PreprocessedData preprocessedData) { + TextContent content = (TextContent) message.getContent(); + tv.setText(content.getText()); + } + } +} diff --git a/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/holders/TextHolderLayouter.java b/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/holders/TextHolderLayouter.java new file mode 100644 index 0000000000..d2aefa0eba --- /dev/null +++ b/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/holders/TextHolderLayouter.java @@ -0,0 +1,48 @@ +package im.actor.holders; + +import android.graphics.Color; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import im.actor.core.entity.Message; +import im.actor.core.entity.Peer; +import im.actor.core.entity.content.AbsContent; +import im.actor.core.entity.content.TextContent; +import im.actor.sdk.controllers.conversation.messages.BubbleLayouter; +import im.actor.sdk.controllers.conversation.messages.MessagesAdapter; +import im.actor.sdk.controllers.conversation.messages.content.AbsMessageViewHolder; +import im.actor.sdk.controllers.conversation.messages.content.preprocessor.PreprocessedData; + +public class TextHolderLayouter implements BubbleLayouter { + @Override + public boolean isMatch(AbsContent content) { + return content instanceof TextContent; + } + + @Override + public AbsMessageViewHolder onCreateViewHolder(MessagesAdapter adapter, ViewGroup root, Peer peer) { + return new TextHolderEx(new TextView(root.getContext())); + } + + private class TextHolderEx extends AbsMessageViewHolder { + TextView tv; + + public TextHolderEx(View itemView) { + super(itemView); + tv = (TextView) itemView; + tv.setTextColor(Color.RED); + } + + @Override + public void bindData(Message message, Message prev, Message next, long readDate, long receiveDate, PreprocessedData preprocessedData) { + TextContent content = (TextContent) message.getContent(); + tv.setText(content.getText()); + } + + @Override + public void unbind() { + + } + } +} diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorSDK.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorSDK.java index 35f21e58ac..47a91d29ef 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorSDK.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorSDK.java @@ -8,7 +8,6 @@ import android.content.Intent; import android.os.Build; import android.os.Bundle; -import android.view.ViewGroup; import com.facebook.drawee.backends.pipeline.Fresco; import com.facebook.imagepipeline.core.ImagePipelineConfig; @@ -29,13 +28,10 @@ import im.actor.runtime.Log; import im.actor.runtime.Runtime; import im.actor.runtime.actors.ActorSystem; -import im.actor.runtime.android.view.BindedViewHolder; import im.actor.runtime.threading.ThreadDispatcher; import im.actor.sdk.controllers.Intents; import im.actor.sdk.controllers.root.RootActivity; import im.actor.sdk.controllers.conversation.ChatActivity; -import im.actor.sdk.controllers.conversation.messages.content.MessageHolder; -import im.actor.sdk.controllers.conversation.messages.MessagesAdapter; import im.actor.sdk.controllers.auth.AuthActivity; import im.actor.sdk.controllers.group.GroupInfoActivity; import im.actor.sdk.controllers.settings.MyProfileActivity; @@ -1049,29 +1045,6 @@ public T getDelegatedFragment(ActorIntent delegatedIntent, android.support.v } - /** - * Method is used internally for getting delegated list ViewHolder for default messages types - */ - public T getDelegatedViewHolder(Class base, OnDelegateViewHolder callback, Object... args) { - T delegated = delegate.getViewHolder(base, args); - if (delegated != null) { - return delegated; - } else { - return callback.onNotDelegated(); - } - } - - /** - * Method is used internally for getting delegated list ViewHolder for custom messages types - */ - public MessageHolder getDelegatedCustomMessageViewHolder(int dataTypeHash, OnDelegateViewHolder callback, MessagesAdapter messagesAdapter, ViewGroup viewGroup) { - MessageHolder delegated = delegate.getCustomMessageViewHolder(dataTypeHash, messagesAdapter, viewGroup); - if (delegated != null) { - return delegated; - } else { - return callback.onNotDelegated(); - } - } public boolean isVideoCallsEnabled() { return videoCallsEnabled; @@ -1089,13 +1062,6 @@ public void setInviteDataUrl(String inviteDataUrl) { this.inviteDataUrl = inviteDataUrl; } - /** - * Used for handling delegated ViewHolders - */ - public interface OnDelegateViewHolder { - T onNotDelegated(); - - } public static void returnToRoot(Context context) { Intent i; diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorSDKDelegate.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorSDKDelegate.java index de357a4a52..6911161276 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorSDKDelegate.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorSDKDelegate.java @@ -2,21 +2,19 @@ import android.net.Uri; import android.support.v4.app.Fragment; -import android.view.ViewGroup; import org.jetbrains.annotations.Nullable; +import java.util.ArrayList; + import im.actor.core.RawUpdatesHandler; import im.actor.core.entity.Peer; -import im.actor.runtime.android.view.BindedViewHolder; import im.actor.sdk.controllers.conversation.ChatFragment; import im.actor.sdk.controllers.conversation.attach.AbsAttachFragment; import im.actor.sdk.controllers.conversation.inputbar.InputBarFragment; import im.actor.sdk.controllers.conversation.mentions.AutocompleteFragment; -import im.actor.sdk.controllers.conversation.messages.content.MessageHolder; -import im.actor.sdk.controllers.conversation.messages.MessagesAdapter; +import im.actor.sdk.controllers.conversation.messages.BubbleLayouter; import im.actor.sdk.controllers.conversation.quote.QuoteFragment; -import im.actor.sdk.controllers.settings.BaseGroupInfoActivity; import im.actor.sdk.intents.ActorIntent; import im.actor.sdk.intents.ActorIntentFragmentActivity; @@ -134,29 +132,6 @@ public interface ActorSDKDelegate { @Nullable Fragment fragmentForToolbar(Peer peer); - - /** - * Override for hacking default messages view holders - * - * @param base base view holder class - * @param args args passed to view holder - * @param base view holder class - * @param return class - * @return hacked view holder - */ - J getViewHolder(Class base, Object... args); - - /** - * Override for hacking custom messages view holders - * - * @param dataTypeHash json dataType hash - * @param messagesAdapter adapter to pass to holder - * @param viewGroup ViewGroup to pass to holder - * @return custom view holder - */ - MessageHolder getCustomMessageViewHolder(int dataTypeHash, MessagesAdapter messagesAdapter, ViewGroup viewGroup); - - // // Settings // @@ -235,4 +210,11 @@ public interface ActorSDKDelegate { * @return RawUpdatesHandler actor */ RawUpdatesHandler getRawUpdatesHandler(); + + /** + * Override/add new messages view holders + * + * @param layouters default layouters + */ + void configureChatViewHolders(ArrayList layouters); } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/BaseActorSDKDelegate.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/BaseActorSDKDelegate.java index 9542d2e2c3..edf1ff5a7e 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/BaseActorSDKDelegate.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/BaseActorSDKDelegate.java @@ -3,19 +3,18 @@ import android.net.Uri; import android.provider.Settings; import android.support.v4.app.Fragment; -import android.view.ViewGroup; import org.jetbrains.annotations.Nullable; +import java.util.ArrayList; + import im.actor.core.RawUpdatesHandler; import im.actor.core.entity.Peer; -import im.actor.runtime.android.view.BindedViewHolder; import im.actor.sdk.controllers.conversation.ChatFragment; import im.actor.sdk.controllers.conversation.attach.AbsAttachFragment; import im.actor.sdk.controllers.conversation.inputbar.InputBarFragment; import im.actor.sdk.controllers.conversation.mentions.AutocompleteFragment; -import im.actor.sdk.controllers.conversation.messages.MessagesAdapter; -import im.actor.sdk.controllers.conversation.messages.content.MessageHolder; +import im.actor.sdk.controllers.conversation.messages.BubbleLayouter; import im.actor.sdk.controllers.conversation.quote.QuoteFragment; import im.actor.sdk.intents.ActorIntent; import im.actor.sdk.intents.ActorIntentFragmentActivity; @@ -120,16 +119,6 @@ public ActorIntent getChatIntent(Peer peer, boolean compose) { return null; } - @Override - public J getViewHolder(Class base, Object[] args) { - return null; - } - - @Override - public MessageHolder getCustomMessageViewHolder(int dataTypeHash, MessagesAdapter messagesAdapter, ViewGroup viewGroup) { - return null; - } - public Uri getNotificationSoundForPeer(Peer peer) { String globalSound = messenger().getPreferences().getString("userNotificationSound_" + peer.getPeerId()); @@ -169,4 +158,8 @@ public int getNotificationColor() { public RawUpdatesHandler getRawUpdatesHandler() { return null; } + + @Override + public void configureChatViewHolders(ArrayList layouters) { + } } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/BubbleLayouter.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/BubbleLayouter.java new file mode 100644 index 0000000000..61ea344d0e --- /dev/null +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/BubbleLayouter.java @@ -0,0 +1,14 @@ +package im.actor.sdk.controllers.conversation.messages; + +import android.view.ViewGroup; + +import im.actor.core.entity.Peer; +import im.actor.core.entity.content.AbsContent; +import im.actor.sdk.controllers.conversation.messages.content.AbsMessageViewHolder; + +public interface BubbleLayouter { + + boolean isMatch(AbsContent content); + + AbsMessageViewHolder onCreateViewHolder(MessagesAdapter adapter, ViewGroup root, Peer peer); +} \ No newline at end of file diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/MessagesAdapter.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/MessagesAdapter.java index ab40cf2378..cf56806968 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/MessagesAdapter.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/MessagesAdapter.java @@ -1,20 +1,18 @@ package im.actor.sdk.controllers.conversation.messages; import android.content.Context; -import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import java.util.ArrayList; import java.util.HashMap; -import im.actor.core.entity.GroupType; import im.actor.core.entity.Message; -import im.actor.core.entity.PeerType; +import im.actor.core.entity.Peer; import im.actor.core.entity.content.AbsContent; import im.actor.core.entity.content.AnimationContent; import im.actor.core.entity.content.ContactContent; import im.actor.core.entity.content.DocumentContent; -import im.actor.core.entity.content.JsonContent; import im.actor.core.entity.content.LocationContent; import im.actor.core.entity.content.PhotoContent; import im.actor.core.entity.content.ServiceContent; @@ -24,8 +22,6 @@ import im.actor.core.entity.content.VoiceContent; import im.actor.core.viewmodel.ConversationVM; import im.actor.runtime.generic.mvvm.BindedDisplayList; -import im.actor.runtime.json.JSONException; -import im.actor.runtime.json.JSONObject; import im.actor.runtime.mvvm.Value; import im.actor.runtime.mvvm.ValueChangedListener; import im.actor.sdk.ActorSDK; @@ -35,43 +31,98 @@ import im.actor.sdk.controllers.conversation.messages.content.ContactHolder; import im.actor.sdk.controllers.conversation.messages.content.DocHolder; import im.actor.sdk.controllers.conversation.messages.content.LocationHolder; -import im.actor.sdk.controllers.conversation.messages.content.MessageHolder; +import im.actor.sdk.controllers.conversation.messages.content.AbsMessageViewHolder; import im.actor.sdk.controllers.conversation.messages.content.PhotoHolder; import im.actor.sdk.controllers.conversation.messages.content.preprocessor.PreprocessedList; import im.actor.sdk.controllers.conversation.messages.content.ServiceHolder; import im.actor.sdk.controllers.conversation.messages.content.StickerHolder; import im.actor.sdk.controllers.conversation.messages.content.TextHolder; -import im.actor.sdk.controllers.conversation.messages.content.UnsupportedHolder; import im.actor.sdk.controllers.ActorBinder; +import im.actor.sdk.util.ViewUtils; -import static im.actor.sdk.util.ActorSDKMessenger.groups; import static im.actor.sdk.util.ActorSDKMessenger.messenger; -public class MessagesAdapter extends BindedListAdapter { +public class MessagesAdapter extends BindedListAdapter { + + public static final int TEXT_CONTENT = 0; + public static final int SERVICE_CONTENT = 1; + public static final int PHOTO_CONTENT = 2; + public static final int VOICE_CONTENT = 4; + public static final int DOCUMENT_CONTENT = 3; + public static final int CONTACT_CONTENT = 5; + public static final int LOCATION_CONTENT = 6; + public static final int STICKER_CONTENT = 7; private MessagesFragment messagesFragment; private ActorBinder BINDER = new ActorBinder(); private Context context; - private long firstUnread = -1; + private long firstUnread = -SERVICE_CONTENT; private long readDate; private long receiveDate; - private boolean isChannel; + private Peer peer; + private ViewHolderMatcher matcher; private HashMap selected = new HashMap<>(); + private static ArrayList holderMap; + + static { + holderMap = new ArrayList<>(); + holderMap.add(new HolderMapEntry(TextContent.class, TEXT_CONTENT)); + holderMap.add(new HolderMapEntry(ServiceContent.class, SERVICE_CONTENT)); + holderMap.add(new HolderMapEntry(PhotoContent.class, PHOTO_CONTENT)); + holderMap.add(new HolderMapEntry(VideoContent.class, PHOTO_CONTENT)); + holderMap.add(new HolderMapEntry(AnimationContent.class, PHOTO_CONTENT)); + holderMap.add(new HolderMapEntry(VoiceContent.class, VOICE_CONTENT)); + holderMap.add(new HolderMapEntry(DocumentContent.class, DOCUMENT_CONTENT)); + holderMap.add(new HolderMapEntry(ContactContent.class, CONTACT_CONTENT)); + holderMap.add(new HolderMapEntry(LocationContent.class, LOCATION_CONTENT)); + holderMap.add(new HolderMapEntry(StickerContent.class, STICKER_CONTENT)); + } + + private static class HolderMapEntry { + Class aClass; + int id; + + public HolderMapEntry(Class aClass, int id) { + this.aClass = aClass; + this.id = id; + } + + public Class getaClass() { + return aClass; + } + + public int getId() { + return id; + } + } + + public MessagesAdapter(final BindedDisplayList displayList, MessagesFragment messagesFragment, Context context) { super(displayList); + matcher = new ViewHolderMatcher(); + + matcher.add(new DefaultLayouter(TEXT_CONTENT, R.layout.adapter_dialog_text, TextHolder::new)); + matcher.add(new DefaultLayouter(SERVICE_CONTENT, R.layout.adapter_dialog_service, ServiceHolder::new)); + matcher.add(new DefaultLayouter(PHOTO_CONTENT, R.layout.adapter_dialog_photo, PhotoHolder::new)); + matcher.add(new DefaultLayouter(VOICE_CONTENT, R.layout.adapter_dialog_audio, AudioHolder::new)); + matcher.add(new DefaultLayouter(DOCUMENT_CONTENT, R.layout.adapter_dialog_doc, DocHolder::new)); + matcher.add(new DefaultLayouter(CONTACT_CONTENT, R.layout.adapter_dialog_contact, ContactHolder::new)); + matcher.add(new DefaultLayouter(LOCATION_CONTENT, R.layout.adapter_dialog_locaton, LocationHolder::new)); + matcher.add(new DefaultLayouter(STICKER_CONTENT, R.layout.adapter_dialog_sticker, StickerHolder::new)); + + ActorSDK.sharedActor().getDelegate().configureChatViewHolders(matcher.getLayouters()); + + this.messagesFragment = messagesFragment; this.context = context; ConversationVM conversationVM = messenger().getConversationVM(messagesFragment.getPeer()); - isChannel = false; - if (messagesFragment.getPeer().getPeerType() == PeerType.GROUP) { - isChannel = groups().get(messagesFragment.getPeer().getPeerId()).getGroupType() == GroupType.CHANNEL; - } + peer = messagesFragment.getPeer(); readDate = conversationVM.getReadDate().get(); receiveDate = conversationVM.getReceiveDate().get(); @@ -161,136 +212,68 @@ public void setFirstUnread(long firstUnread) { @Override public int getItemViewType(int position) { AbsContent content = getItem(position).getContent(); + return matcher.getMatchId(content); - if (content instanceof TextContent) { - return 0; - } else if (content instanceof ServiceContent) { - return 1; - } else if (content instanceof PhotoContent) { - return 2; - } else if (content instanceof AnimationContent) { - return 2; - } else if (content instanceof VideoContent) { - return 2; - } else if (content instanceof VoiceContent) { - return 4; - } else if (content instanceof DocumentContent) { - return 3; - } else if (content instanceof ContactContent) { - return 5; - } else if (content instanceof LocationContent) { - return 6; - } else if (content instanceof StickerContent) { - return 7; - } else if (content instanceof JsonContent) { - try { - String dataType = new JSONObject(((JsonContent) content).getRawJson()).getString("dataType"); - return dataType.hashCode(); - } catch (JSONException e) { - return -1; - } - } - return -1; } - protected View inflate(int id, ViewGroup viewGroup) { - return LayoutInflater - .from(context) - .inflate(id, viewGroup, false); - } @Override - public MessageHolder onCreateViewHolder(final ViewGroup viewGroup, int viewType) { - switch (viewType) { - case 0: - return ActorSDK.sharedActor().getDelegatedViewHolder(TextHolder.class, new ActorSDK.OnDelegateViewHolder() { - @Override - public TextHolder onNotDelegated() { - return new TextHolder(MessagesAdapter.this, inflate(R.layout.adapter_dialog_text, viewGroup)); - } - }, MessagesAdapter.this, inflate(R.layout.adapter_dialog_text, viewGroup)); - case 1: - return ActorSDK.sharedActor().getDelegatedViewHolder(ServiceHolder.class, new ActorSDK.OnDelegateViewHolder() { - @Override - public ServiceHolder onNotDelegated() { - return new ServiceHolder(MessagesAdapter.this, isChannel, inflate(R.layout.adapter_dialog_service, viewGroup)); - } - }, MessagesAdapter.this, inflate(R.layout.adapter_dialog_service, viewGroup)); - case 2: - return ActorSDK.sharedActor().getDelegatedViewHolder(PhotoHolder.class, new ActorSDK.OnDelegateViewHolder() { - @Override - public PhotoHolder onNotDelegated() { - return new PhotoHolder(MessagesAdapter.this, inflate(R.layout.adapter_dialog_photo, viewGroup)); - } - }, MessagesAdapter.this, inflate(R.layout.adapter_dialog_photo, viewGroup)); - case 3: - return ActorSDK.sharedActor().getDelegatedViewHolder(DocHolder.class, new ActorSDK.OnDelegateViewHolder() { - @Override - public DocHolder onNotDelegated() { - return new DocHolder(MessagesAdapter.this, inflate(R.layout.adapter_dialog_doc, viewGroup)); - } - }, MessagesAdapter.this, inflate(R.layout.adapter_dialog_doc, viewGroup)); - case 4: - return ActorSDK.sharedActor().getDelegatedViewHolder(AudioHolder.class, new ActorSDK.OnDelegateViewHolder() { - @Override - public AudioHolder onNotDelegated() { - return new AudioHolder(MessagesAdapter.this, inflate(R.layout.adapter_dialog_audio, viewGroup)); - } - }, MessagesAdapter.this, inflate(R.layout.adapter_dialog_audio, viewGroup)); - case 5: - return ActorSDK.sharedActor().getDelegatedViewHolder(ContactHolder.class, new ActorSDK.OnDelegateViewHolder() { - @Override - public ContactHolder onNotDelegated() { - return new ContactHolder(MessagesAdapter.this, inflate(R.layout.adapter_dialog_contact, viewGroup)); - } - }, MessagesAdapter.this, inflate(R.layout.adapter_dialog_contact, viewGroup)); - case 6: - return ActorSDK.sharedActor().getDelegatedViewHolder(LocationHolder.class, new ActorSDK.OnDelegateViewHolder() { - @Override - public LocationHolder onNotDelegated() { - return new LocationHolder(MessagesAdapter.this, inflate(R.layout.adapter_dialog_locaton, viewGroup)); - } - }, MessagesAdapter.this, inflate(R.layout.adapter_dialog_locaton, viewGroup)); - case 7: - return ActorSDK.sharedActor().getDelegatedViewHolder(StickerHolder.class, new ActorSDK.OnDelegateViewHolder() { - @Override - public StickerHolder onNotDelegated() { - return new StickerHolder(MessagesAdapter.this, inflate(R.layout.adapter_dialog_sticker, viewGroup)); - } - }, MessagesAdapter.this, inflate(R.layout.adapter_dialog_sticker, viewGroup)); - case -1: - return new UnsupportedHolder(MessagesAdapter.this, inflate(R.layout.adapter_dialog_text, viewGroup)); - default: - return ActorSDK.sharedActor().getDelegatedCustomMessageViewHolder(viewType, new ActorSDK.OnDelegateViewHolder() { - @Override - public MessageHolder onNotDelegated() { - return new UnsupportedHolder(MessagesAdapter.this, inflate(R.layout.adapter_dialog_text, viewGroup)); - } - }, MessagesAdapter.this, viewGroup); - - } + public AbsMessageViewHolder onCreateViewHolder(final ViewGroup viewGroup, int viewType) { + return matcher.onCreateViewHolder(viewType, this, viewGroup, peer); } @Override - public void onBindViewHolder(MessageHolder dialogHolder, int index, Message item) { + public void onBindViewHolder(AbsMessageViewHolder dialogHolder, int index, Message item) { Message prev = null; Message next = null; - if (index > 1) { - next = getItem(index - 1); + if (index > SERVICE_CONTENT) { + next = getItem(index - SERVICE_CONTENT); } - if (index < getItemCount() - 1) { - prev = getItem(index + 1); + if (index < getItemCount() - SERVICE_CONTENT) { + prev = getItem(index + SERVICE_CONTENT); } PreprocessedList list = ((PreprocessedList) getPreprocessedList()); dialogHolder.bindData(item, prev, next, readDate, receiveDate, list.getPreprocessedData()[index]); } @Override - public void onViewRecycled(MessageHolder holder) { + public void onViewRecycled(AbsMessageViewHolder holder) { holder.unbind(); } public ActorBinder getBinder() { return BINDER; } + + private class DefaultLayouter implements BubbleLayouter { + int id; + int layoutId; + HolderCreator holderCreator; + + public DefaultLayouter(int id, int layoutId, HolderCreator holderCreator) { + this.id = id; + this.layoutId = layoutId; + this.holderCreator = holderCreator; + } + + @Override + public boolean isMatch(AbsContent content) { + for (HolderMapEntry e : holderMap) { + if (e.getaClass().isAssignableFrom(content.getClass())) { + return e.getId() == id; + } + } + return false; + } + + @Override + public AbsMessageViewHolder onCreateViewHolder(MessagesAdapter adapter, ViewGroup root, Peer peer) { + return holderCreator.createHolder(adapter, ViewUtils.inflate(layoutId, root), peer); + } + } + + private interface HolderCreator { + AbsMessageViewHolder createHolder(MessagesAdapter adapter, View view, Peer peer); + } + } \ No newline at end of file diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/MessagesFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/MessagesFragment.java index 2e49070e4c..f05b51acab 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/MessagesFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/MessagesFragment.java @@ -18,15 +18,11 @@ import im.actor.core.entity.Message; import im.actor.core.entity.Peer; import im.actor.core.viewmodel.ConversationVM; -import im.actor.runtime.mvvm.Value; -import im.actor.runtime.mvvm.ValueChangedListener; -import im.actor.runtime.mvvm.ValueDoubleChangedListener; import im.actor.sdk.ActorSDK; import im.actor.sdk.R; -import im.actor.sdk.controllers.conversation.ChatActivity; import im.actor.sdk.controllers.conversation.messages.content.AudioHolder; +import im.actor.sdk.controllers.conversation.messages.content.AbsMessageViewHolder; import im.actor.sdk.controllers.conversation.messages.content.preprocessor.ChatListProcessor; -import im.actor.sdk.controllers.conversation.messages.content.MessageHolder; import im.actor.sdk.controllers.DisplayListFragment; import im.actor.sdk.controllers.settings.BaseActorSettingsFragment; import im.actor.sdk.util.Screen; @@ -35,7 +31,7 @@ import static im.actor.sdk.util.ActorSDKMessenger.messenger; -public abstract class MessagesFragment extends DisplayListFragment { +public abstract class MessagesFragment extends DisplayListFragment { private final boolean isPrimaryMode; @@ -149,7 +145,7 @@ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, // Configure RecyclerView // @Override - protected BindedListAdapter onCreateAdapter(BindedDisplayList displayList, Activity activity) { + protected BindedListAdapter onCreateAdapter(BindedDisplayList displayList, Activity activity) { messagesAdapter = new MessagesAdapter(displayList, this, activity); if (firstUnread != -1 && messagesAdapter.getFirstUnread() == -1) { messagesAdapter.setFirstUnread(firstUnread); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/ViewHolderMatcher.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/ViewHolderMatcher.java new file mode 100644 index 0000000000..7a3b1192a1 --- /dev/null +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/ViewHolderMatcher.java @@ -0,0 +1,48 @@ +package im.actor.sdk.controllers.conversation.messages; + +import android.view.ViewGroup; + +import java.util.ArrayList; + +import im.actor.core.entity.Peer; +import im.actor.core.entity.content.AbsContent; +import im.actor.sdk.controllers.conversation.messages.content.AbsMessageViewHolder; +import im.actor.sdk.controllers.conversation.messages.content.UnsupportedHolder; +import im.actor.sdk.util.ViewUtils; +import im.actor.sdk.R; + +public class ViewHolderMatcher { + ArrayList layouters = new ArrayList<>(); + + public ViewHolderMatcher add(BubbleLayouter layouter) { + layouters.add(layouter); + return this; + } + + public ViewHolderMatcher addToTop(BubbleLayouter layouter) { + layouters.add(0, layouter); + return this; + } + + + public int getMatchId(AbsContent content) { + for (int i = 0; i < layouters.size(); i++) { + if (layouters.get(i).isMatch(content)) { + return i; + } + } + return -1; + } + + public AbsMessageViewHolder onCreateViewHolder(int id, MessagesAdapter adapter, ViewGroup root, Peer peer) { + if (id == -1) { + return new UnsupportedHolder(adapter, ViewUtils.inflate(R.layout.adapter_dialog_text, root), peer); + } + BubbleLayouter baseViewHolderMatch = layouters.get(id); + return baseViewHolderMatch.onCreateViewHolder(adapter, root, peer); + } + + public ArrayList getLayouters() { + return layouters; + } +} \ No newline at end of file diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/AbsMessageViewHolder.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/AbsMessageViewHolder.java new file mode 100644 index 0000000000..0e2ede23ac --- /dev/null +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/AbsMessageViewHolder.java @@ -0,0 +1,17 @@ +package im.actor.sdk.controllers.conversation.messages.content; + +import android.view.View; + +import im.actor.core.entity.Message; +import im.actor.runtime.android.view.BindedViewHolder; +import im.actor.sdk.controllers.conversation.messages.content.preprocessor.PreprocessedData; + +public abstract class AbsMessageViewHolder extends BindedViewHolder { + public AbsMessageViewHolder(View itemView) { + super(itemView); + } + + public abstract void bindData(Message message, Message prev, Message next, long readDate, long receiveDate, PreprocessedData preprocessedData); + + public abstract void unbind(); +} diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/AudioHolder.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/AudioHolder.java index 99178aae0d..e23459e075 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/AudioHolder.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/AudioHolder.java @@ -17,6 +17,7 @@ import com.droidkit.progress.CircularView; import im.actor.core.entity.Message; +import im.actor.core.entity.Peer; import im.actor.core.entity.content.DocumentContent; import im.actor.core.entity.content.FileLocalSource; import im.actor.core.entity.content.FileRemoteSource; @@ -70,7 +71,7 @@ public class AudioHolder extends MessageHolder { protected boolean treckingTouch; protected Handler mainThread; - public AudioHolder(MessagesAdapter fragment, final View itemView) { + public AudioHolder(MessagesAdapter fragment, final View itemView, Peer peer) { super(fragment, itemView, false); context = fragment.getMessagesFragment().getContext(); mainThread = new Handler(context.getMainLooper()); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/BaseJsonHolder.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/BaseJsonHolder.java deleted file mode 100644 index bb859a9c2a..0000000000 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/BaseJsonHolder.java +++ /dev/null @@ -1,46 +0,0 @@ -package im.actor.sdk.controllers.conversation.messages.content; - -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.FrameLayout; - -import im.actor.core.entity.Message; -import im.actor.core.entity.content.JsonContent; -import im.actor.runtime.json.JSONException; -import im.actor.runtime.json.JSONObject; -import im.actor.sdk.R; -import im.actor.sdk.controllers.conversation.messages.MessagesAdapter; -import im.actor.sdk.controllers.conversation.messages.content.preprocessor.PreprocessedData; - -public abstract class BaseJsonHolder extends MessageHolder { - - public BaseJsonHolder(MessagesAdapter adapter, ViewGroup viewGroup, int resourceId, boolean isFullSize) { - super(adapter, inflate(resourceId, viewGroup), isFullSize); - } - - @Override - protected void bindData(Message message, long readDate, long receiveDate, boolean isUpdated, PreprocessedData preprocessedData) { - JSONObject json = null; - JSONObject data = null; - - try { - json = new JSONObject(((JsonContent) message.getContent()).getRawJson()); - - data = json.getJSONObject("data"); - } catch (JSONException e) { - e.printStackTrace(); - } - - bindData(message, data, isUpdated, preprocessedData); - } - - protected abstract void bindData(Message message, JSONObject data, boolean isUpdated, PreprocessedData preprocessedData); - - private static View inflate(int resourceId, ViewGroup viewGroup) { - View base = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.custom_holder, viewGroup, false); - View content = LayoutInflater.from(viewGroup.getContext()).inflate(resourceId, viewGroup, false); - ((FrameLayout) base.findViewById(R.id.custom_container)).addView(content); - return base; - } -} diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/ContactHolder.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/ContactHolder.java index 4cecd4f7a9..5afcff9a27 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/ContactHolder.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/ContactHolder.java @@ -18,6 +18,7 @@ import android.widget.TextView; import im.actor.core.entity.Message; +import im.actor.core.entity.Peer; import im.actor.core.entity.content.ContactContent; import im.actor.sdk.ActorSDK; import im.actor.sdk.R; @@ -47,7 +48,7 @@ public class ContactHolder extends MessageHolder { private ImageView contactAvatar; - public ContactHolder(MessagesAdapter fragment, final View itemView) { + public ContactHolder(MessagesAdapter fragment, final View itemView, Peer peer) { super(fragment, itemView, false); waitColor = ActorSDK.sharedActor().style.getConvStatePendingColor(); sentColor = ActorSDK.sharedActor().style.getConvStateSentColor(); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/DocHolder.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/DocHolder.java index e94976ce5a..6f7361795c 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/DocHolder.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/DocHolder.java @@ -13,6 +13,7 @@ import im.actor.core.entity.FileReference; import im.actor.core.entity.Message; +import im.actor.core.entity.Peer; import im.actor.core.entity.content.DocumentContent; import im.actor.core.entity.content.FileLocalSource; import im.actor.core.entity.content.FileRemoteSource; @@ -68,11 +69,11 @@ public class DocHolder extends MessageHolder { protected UploadFileVM uploadFileVM; protected DocumentContent document; - public DocHolder(final MessagesAdapter fragment, View itemView) { - this(fragment, itemView, false); + public DocHolder(final MessagesAdapter fragment, View itemView, Peer peer) { + this(fragment, itemView, false, peer); } - public DocHolder(final MessagesAdapter fragment, View itemView, boolean isFullSize) { + public DocHolder(final MessagesAdapter fragment, View itemView, boolean isFullSize, Peer peer) { super(fragment, itemView, isFullSize); waitColor = ActorSDK.sharedActor().style.getConvStatePendingColor(); sentColor = ActorSDK.sharedActor().style.getConvStateSentColor(); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/LocationHolder.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/LocationHolder.java index 2d5700f82a..9fe43d5b53 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/LocationHolder.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/LocationHolder.java @@ -29,6 +29,7 @@ import java.io.OutputStream; import im.actor.core.entity.Message; +import im.actor.core.entity.Peer; import im.actor.core.entity.content.LocationContent; import im.actor.core.viewmodel.FileVM; import im.actor.core.viewmodel.UploadFileVM; @@ -66,7 +67,7 @@ public class LocationHolder extends MessageHolder { protected UploadFileVM uploadFileVM; protected boolean isPhoto; - public LocationHolder(MessagesAdapter fragment, View itemView) { + public LocationHolder(MessagesAdapter fragment, View itemView, Peer peer) { super(fragment, itemView, false); this.context = fragment.getMessagesFragment().getActivity(); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/MessageHolder.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/MessageHolder.java index 47163afe9b..f62ddffdbd 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/MessageHolder.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/MessageHolder.java @@ -12,7 +12,6 @@ import im.actor.sdk.controllers.conversation.view.BubbleContainer; import im.actor.sdk.controllers.conversation.view.ReactionSpan; import im.actor.sdk.util.DateFormatting; -import im.actor.runtime.android.view.BindedViewHolder; import im.actor.core.entity.Message; import im.actor.core.entity.Peer; import im.actor.core.entity.PeerType; @@ -20,7 +19,7 @@ import static im.actor.sdk.util.ActorSDKMessenger.myUid; import static im.actor.sdk.util.ActorSDKMessenger.users; -public abstract class MessageHolder extends BindedViewHolder +public abstract class MessageHolder extends AbsMessageViewHolder implements BubbleContainer.OnAvatarClickListener, BubbleContainer.OnAvatarLongClickListener, View.OnClickListener, View.OnLongClickListener { protected MessagesAdapter adapter; @@ -58,6 +57,7 @@ public Peer getPeer() { return adapter.getMessagesFragment().getPeer(); } + @Override public final void bindData(Message message, Message prev, Message next, long readDate, long receiveDate, PreprocessedData preprocessedData) { boolean isUpdated = currentMessage == null || currentMessage.getRid() != message.getRid(); currentMessage = message; @@ -147,7 +147,7 @@ public boolean onLongClick(View v) { return false; } - + @Override public void unbind() { currentMessage = null; } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/PhotoHolder.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/PhotoHolder.java index e779e351ad..1d00fd1358 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/PhotoHolder.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/PhotoHolder.java @@ -29,6 +29,7 @@ import im.actor.core.entity.FileReference; import im.actor.core.entity.Message; +import im.actor.core.entity.Peer; import im.actor.core.entity.content.AnimationContent; import im.actor.core.entity.content.DocumentContent; import im.actor.core.entity.content.FileLocalSource; @@ -101,7 +102,7 @@ public class PhotoHolder extends MessageHolder { private final ControllerListener animationController; private Animatable anim; - public PhotoHolder(MessagesAdapter fragment, View itemView) { + public PhotoHolder(MessagesAdapter fragment, View itemView, Peer peer) { super(fragment, itemView, false); this.context = fragment.getMessagesFragment().getActivity(); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/ServiceHolder.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/ServiceHolder.java index c0814485f4..9cfe6e91a5 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/ServiceHolder.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/ServiceHolder.java @@ -3,6 +3,9 @@ import android.view.View; import android.widget.TextView; +import im.actor.core.entity.GroupType; +import im.actor.core.entity.Peer; +import im.actor.core.entity.PeerType; import im.actor.sdk.ActorSDK; import im.actor.sdk.R; import im.actor.core.entity.Message; @@ -10,6 +13,7 @@ import im.actor.sdk.controllers.conversation.messages.MessagesAdapter; import im.actor.sdk.controllers.conversation.messages.content.preprocessor.PreprocessedData; +import static im.actor.sdk.util.ActorSDKMessenger.groups; import static im.actor.sdk.util.ActorSDKMessenger.messenger; public class ServiceHolder extends MessageHolder { @@ -17,10 +21,10 @@ public class ServiceHolder extends MessageHolder { private TextView messageText; private boolean isChannel; - public ServiceHolder(MessagesAdapter fragment, boolean isChannel, View itemView) { + public ServiceHolder(MessagesAdapter fragment, View itemView, Peer peer) { super(fragment, itemView, true); - this.isChannel = isChannel; + isChannel = peer.getPeerType() == PeerType.GROUP && groups().get(peer.getPeerId()).getGroupType() == GroupType.CHANNEL; messageText = (TextView) itemView.findViewById(R.id.serviceMessage); messageText.setTextColor(ActorSDK.sharedActor().style.getConvDatetextColor()); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/StickerHolder.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/StickerHolder.java index ebc7fdf26c..de2f247ef6 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/StickerHolder.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/StickerHolder.java @@ -8,6 +8,7 @@ import im.actor.core.entity.FileReference; import im.actor.core.entity.ImageLocation; import im.actor.core.entity.Message; +import im.actor.core.entity.Peer; import im.actor.core.entity.content.StickerContent; import im.actor.sdk.ActorSDK; import im.actor.sdk.R; @@ -40,7 +41,7 @@ public class StickerHolder extends MessageHolder { // Content Views private StickerView sticker; - public StickerHolder(MessagesAdapter fragment, View itemView) { + public StickerHolder(MessagesAdapter fragment, View itemView, Peer peer) { super(fragment, itemView, false); this.context = fragment.getMessagesFragment().getActivity(); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/TextHolder.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/TextHolder.java index 5a67ce9ded..77bf45a840 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/TextHolder.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/TextHolder.java @@ -9,6 +9,7 @@ import android.widget.TextView; import im.actor.core.entity.Message; +import im.actor.core.entity.Peer; import im.actor.sdk.ActorSDK; import im.actor.sdk.R; import im.actor.sdk.controllers.conversation.messages.MessagesAdapter; @@ -33,7 +34,7 @@ public class TextHolder extends MessageHolder { private int readColor; private int errorColor; - public TextHolder(MessagesAdapter fragment, final View itemView) { + public TextHolder(MessagesAdapter fragment, final View itemView, Peer peer) { super(fragment, itemView, false); mainContainer = (ViewGroup) itemView.findViewById(R.id.mainContainer); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/UnsupportedHolder.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/UnsupportedHolder.java index dde71c5305..0cd03209ab 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/UnsupportedHolder.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/UnsupportedHolder.java @@ -6,6 +6,7 @@ import android.view.View; +import im.actor.core.entity.Peer; import im.actor.sdk.R; import im.actor.core.entity.Message; import im.actor.sdk.controllers.conversation.messages.MessagesAdapter; @@ -15,8 +16,8 @@ public class UnsupportedHolder extends TextHolder { protected String text; - public UnsupportedHolder(MessagesAdapter fragment, View itemView) { - super(fragment, itemView); + public UnsupportedHolder(MessagesAdapter fragment, View itemView, Peer peer) { + super(fragment, itemView, peer); text = fragment.getMessagesFragment().getResources().getString(R.string.chat_unsupported); onConfigureViewHolder(); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/util/ViewUtils.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/util/ViewUtils.java index b9ecaef873..db4a143cb0 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/util/ViewUtils.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/util/ViewUtils.java @@ -1,6 +1,9 @@ package im.actor.sdk.util; +import android.support.annotation.LayoutRes; +import android.view.LayoutInflater; import android.view.View; +import android.view.ViewGroup; import android.view.animation.AlphaAnimation; import android.view.animation.Animation; import android.view.animation.ScaleAnimation; @@ -17,6 +20,12 @@ public static void goneView(final View view, boolean isAnimated) { goneView(view, isAnimated, true); } + public static View inflate(@LayoutRes int id, ViewGroup viewGroup) { + return LayoutInflater + .from(viewGroup.getContext()) + .inflate(id, viewGroup, false); + } + public static void goneView(final View view, boolean isAnimated, boolean isSlow) { if (view == null) { return; diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/custom_holder.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/custom_holder.xml index 425c319fe2..973f940d2b 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/custom_holder.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/custom_holder.xml @@ -8,20 +8,9 @@ android:layout_height="wrap_content" android:clickable="true"> - - - - - - - + \ No newline at end of file diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/KeyManagerActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/KeyManagerActor.java index 3a0a3f97a5..9a6501c501 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/KeyManagerActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/KeyManagerActor.java @@ -367,7 +367,7 @@ public PublicKey apply(ResponsePublicKeys responsePublicKeys) { if (!Curve25519.verifySignature(keysGroup.getT1().getIdentityKey().getPublicKey(), keyHash, sig.getSignature())) { - throw new RuntimeException("Key signature does not match"); + throw new RuntimeException("Key signature does not isMatch"); } PublicKey pkey = new PublicKey(keyId, key.getKeyAlg(), key.getKeyMaterial()); @@ -417,7 +417,7 @@ public PublicKey apply(ResponsePublicKeys response) { if (!Curve25519.verifySignature(keyGroups.getT1().getIdentityKey().getPublicKey(), keyHash, sig.getSignature())) { - throw new RuntimeException("Key signature does not match"); + throw new RuntimeException("Key signature does not isMatch"); } return new PublicKey(key.getKeyId(), key.getKeyAlg(), key.getKeyMaterial()); diff --git a/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/crypto/box/ActorBox.java b/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/crypto/box/ActorBox.java index f98143f1f2..84b44a2d7e 100644 --- a/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/crypto/box/ActorBox.java +++ b/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/crypto/box/ActorBox.java @@ -51,7 +51,7 @@ public static byte[] openBox(byte[] header, byte[] cipherText, ActorBoxKey key) } PKCS7Padding padding = new PKCS7Padding(); if (!padding.validate(plainText, plainText.length - 1 - paddingSize, paddingSize)) { - throw new IntegrityException("Padding does not match!"); + throw new IntegrityException("Padding does not isMatch!"); } return ByteStrings.substring(plainText, 0, plainText.length - 1 - paddingSize); diff --git a/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/markdown/Patterns.java b/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/markdown/Patterns.java index e57e2d80ca..a4f0e3629c 100644 --- a/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/markdown/Patterns.java +++ b/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/markdown/Patterns.java @@ -63,7 +63,7 @@ public class Patterns { public static final String WEB_URL_START_CHAR = "^" + WEB_URL_CHAR; /** - * Regular expression pattern to match most part of RFC 3987 + * Regular expression pattern to isMatch most part of RFC 3987 * Internationalized URLs, aka IRIs. Commonly used Unicode characters are * added. */ @@ -130,7 +130,7 @@ public static final String concatGroups(MatcherCompat matcher) { * be extracted * * @return A String comprising all of the digits and plus in - * the match + * the isMatch */ public static final String digitsAndPlusOnly(MatcherCompat matcher) { StringBuilder buffer = new StringBuilder(); From 51b636f9ba2f58a459e358843db0e31fe00f93bc Mon Sep 17 00:00:00 2001 From: kor_ka Date: Fri, 19 Aug 2016 16:44:33 +0300 Subject: [PATCH 290/414] chore(android): add json layouter --- .../src/main/java/im/actor/Application.java | 40 ++++++++++++++++++- .../main/java/im/actor/sdk/ActorStyle.java | 4 ++ .../messages/JsonBubbleLayouter.java | 39 ++++++++++++++++++ .../messages/JsonXmlBubbleLayouter.java | 31 ++++++++++++++ .../messages/LambdaBubbleLayouter.java | 37 +++++++++++++++++ .../messages/MessagesAdapter.java | 24 ++++++----- .../messages/XmlBubbleLayouter.java | 26 ++++++++++++ .../java/im/actor/sdk/util/ViewUtils.java | 6 ++- 8 files changed, 194 insertions(+), 13 deletions(-) create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/JsonBubbleLayouter.java create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/JsonXmlBubbleLayouter.java create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/LambdaBubbleLayouter.java create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/XmlBubbleLayouter.java diff --git a/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/Application.java b/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/Application.java index c853f6538f..f21c2f1551 100644 --- a/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/Application.java +++ b/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/Application.java @@ -1,5 +1,10 @@ package im.actor; +import android.content.Intent; +import android.graphics.Color; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.net.Uri; import android.os.Bundle; import android.support.multidex.MultiDex; import android.support.v4.app.Fragment; @@ -7,6 +12,7 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.Button; import android.widget.CheckBox; import android.widget.Toast; @@ -15,9 +21,14 @@ import java.util.ArrayList; import java.util.List; +import im.actor.core.entity.Message; import im.actor.core.entity.Peer; +import im.actor.core.entity.content.JsonContent; +import im.actor.core.entity.content.PhotoContent; import im.actor.develop.R; import im.actor.holders.BubbleTextHolderLayouter; +import im.actor.runtime.json.JSONException; +import im.actor.runtime.json.JSONObject; import im.actor.sdk.ActorSDK; import im.actor.sdk.ActorSDKApplication; import im.actor.sdk.ActorStyle; @@ -26,6 +37,11 @@ import im.actor.sdk.controllers.conversation.attach.AbsAttachFragment; import im.actor.sdk.controllers.conversation.attach.AttachFragment; import im.actor.sdk.controllers.conversation.messages.BubbleLayouter; +import im.actor.sdk.controllers.conversation.messages.JsonXmlBubbleLayouter; +import im.actor.sdk.controllers.conversation.messages.XmlBubbleLayouter; +import im.actor.sdk.controllers.conversation.messages.content.PhotoHolder; +import im.actor.sdk.controllers.conversation.messages.content.TextHolder; +import im.actor.sdk.controllers.conversation.messages.content.preprocessor.PreprocessedData; import im.actor.sdk.controllers.root.RootFragment; import im.actor.sdk.controllers.settings.ActorSettingsCategories; import im.actor.sdk.controllers.settings.ActorSettingsCategory; @@ -92,7 +108,29 @@ private class ActorSDKDelegate extends BaseActorSDKDelegate { @Override public void configureChatViewHolders(ArrayList layouters) { - layouters.add(0, new BubbleTextHolderLayouter()); +// layouters.add(0, new BubbleTextHolderLayouter()); + layouters.add(0, new XmlBubbleLayouter(content -> content instanceof PhotoContent, R.layout.adapter_dialog_photo, (adapter1, root1, peer1) -> new PhotoHolder(adapter1, root1, peer1) { + @Override + protected void onConfigureViewHolder() { + previewView.setColorFilter(ActorStyle.adjustColorAlpha(Color.CYAN, 20), PorterDuff.Mode.ADD); + } + })); + layouters.add(0, new JsonXmlBubbleLayouter(null, R.layout.adapter_dialog_text, (adapter, root, peer) -> new TextHolder(adapter, root, peer) { + @Override + protected void bindData(Message message, long readDate, long receiveDate, boolean isUpdated, PreprocessedData preprocessedData) { + String jsonString = "can't read json"; + try { + JSONObject jsonObject = new JSONObject(((JsonContent) message.getContent()).getRawJson()); + String dataType = jsonObject.getString("dataType"); + JSONObject data = jsonObject.getJSONObject("data"); + jsonString = dataType + "\n\n"; + jsonString += data.toString(3); + } catch (JSONException e) { + e.printStackTrace(); + } + bindRawText(jsonString, readDate, receiveDate, reactions, message, false); + } + })); } @Nullable diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorStyle.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorStyle.java index 95a729f7ff..fac4075596 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorStyle.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorStyle.java @@ -924,6 +924,10 @@ public static int getDarkenArgb(int color, double percent) { return Color.argb(Color.alpha(color), (int) Math.round(Color.red(color) * percent), (int) Math.round(Color.green(color) * percent), (int) Math.round(Color.blue(color) * percent)); } + public static int adjustColorAlpha(int color, int alpha) { + return (alpha << 24) | (color & 0x00ffffff); + } + /** * Get color with fallback to default - if color is 0, returns fallback color * diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/JsonBubbleLayouter.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/JsonBubbleLayouter.java new file mode 100644 index 0000000000..7b33e0bfb2 --- /dev/null +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/JsonBubbleLayouter.java @@ -0,0 +1,39 @@ +package im.actor.sdk.controllers.conversation.messages; + +import android.view.ViewGroup; + +import org.jetbrains.annotations.NotNull; + +import im.actor.core.entity.Peer; +import im.actor.core.entity.content.AbsContent; +import im.actor.core.entity.content.JsonContent; +import im.actor.runtime.json.JSONException; +import im.actor.runtime.json.JSONObject; +import im.actor.sdk.controllers.conversation.messages.content.AbsMessageViewHolder; + +public class JsonBubbleLayouter extends LambdaBubbleLayouter { + + + public JsonBubbleLayouter(String dataType, @NotNull LambdaBubbleLayouter.ViewHolderCreator creator) { + super(content -> + { + if (content instanceof JsonContent) { + if (dataType == null) { + return true; + } + try { + return dataType.equals(new JSONObject(((JsonContent) content).getRawJson()).getString("dataType")); + } catch (JSONException e) { + e.printStackTrace(); + } + } + return false; + }, creator); + } + + @Override + public boolean isMatch(AbsContent content) { + return matcher.isMatch(content); + } + +} diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/JsonXmlBubbleLayouter.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/JsonXmlBubbleLayouter.java new file mode 100644 index 0000000000..3e068d9cf2 --- /dev/null +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/JsonXmlBubbleLayouter.java @@ -0,0 +1,31 @@ +package im.actor.sdk.controllers.conversation.messages; + +import android.support.annotation.LayoutRes; +import android.view.ViewGroup; + +import org.jetbrains.annotations.NotNull; + +import im.actor.core.entity.Peer; +import im.actor.core.entity.content.AbsContent; +import im.actor.core.entity.content.JsonContent; +import im.actor.runtime.json.JSONException; +import im.actor.runtime.json.JSONObject; +import im.actor.sdk.controllers.conversation.messages.content.AbsMessageViewHolder; +import im.actor.sdk.controllers.conversation.view.BubbleContainer; +import im.actor.sdk.util.ViewUtils; + +public class JsonXmlBubbleLayouter extends JsonBubbleLayouter { + + private int id; + + public JsonXmlBubbleLayouter(String dataType, @LayoutRes int id, @NotNull ViewHolderCreator creator) { + super(dataType, creator); + this.id = id; + } + + @Override + public AbsMessageViewHolder onCreateViewHolder(MessagesAdapter adapter, ViewGroup root, Peer peer) { + return creator.onCreateViewHolder(adapter, (ViewGroup) ViewUtils.inflate(id, root), peer); + } + +} diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/LambdaBubbleLayouter.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/LambdaBubbleLayouter.java new file mode 100644 index 0000000000..c9266dbcb9 --- /dev/null +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/LambdaBubbleLayouter.java @@ -0,0 +1,37 @@ +package im.actor.sdk.controllers.conversation.messages; + +import android.view.ViewGroup; + +import org.jetbrains.annotations.NotNull; + +import im.actor.core.entity.Peer; +import im.actor.core.entity.content.AbsContent; +import im.actor.sdk.controllers.conversation.messages.content.AbsMessageViewHolder; + +public class LambdaBubbleLayouter implements BubbleLayouter { + protected Matcher matcher; + protected ViewHolderCreator creator; + + public LambdaBubbleLayouter(@NotNull Matcher matcher, @NotNull ViewHolderCreator creator) { + this.matcher = matcher; + this.creator = creator; + } + + @Override + public boolean isMatch(AbsContent content) { + return matcher.isMatch(content); + } + + @Override + public AbsMessageViewHolder onCreateViewHolder(MessagesAdapter adapter, ViewGroup root, Peer peer) { + return creator.onCreateViewHolder(adapter, root, peer); + } + + public interface Matcher { + boolean isMatch(AbsContent content); + } + + public interface ViewHolderCreator { + AbsMessageViewHolder onCreateViewHolder(MessagesAdapter adapter, ViewGroup root, Peer peer); + } +} diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/MessagesAdapter.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/MessagesAdapter.java index cf56806968..e70148f472 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/MessagesAdapter.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/MessagesAdapter.java @@ -69,25 +69,27 @@ public class MessagesAdapter extends BindedListAdapter(); - holderMap.add(new HolderMapEntry(TextContent.class, TEXT_CONTENT)); - holderMap.add(new HolderMapEntry(ServiceContent.class, SERVICE_CONTENT)); - holderMap.add(new HolderMapEntry(PhotoContent.class, PHOTO_CONTENT)); - holderMap.add(new HolderMapEntry(VideoContent.class, PHOTO_CONTENT)); - holderMap.add(new HolderMapEntry(AnimationContent.class, PHOTO_CONTENT)); - holderMap.add(new HolderMapEntry(VoiceContent.class, VOICE_CONTENT)); - holderMap.add(new HolderMapEntry(DocumentContent.class, DOCUMENT_CONTENT)); - holderMap.add(new HolderMapEntry(ContactContent.class, CONTACT_CONTENT)); - holderMap.add(new HolderMapEntry(LocationContent.class, LOCATION_CONTENT)); - holderMap.add(new HolderMapEntry(StickerContent.class, STICKER_CONTENT)); + holderMap.add(new HolderMapEntry(TextContent.class, TEXT_CONTENT, R.layout.adapter_dialog_text)); + holderMap.add(new HolderMapEntry(ServiceContent.class, SERVICE_CONTENT, R.layout.adapter_dialog_service)); + holderMap.add(new HolderMapEntry(PhotoContent.class, PHOTO_CONTENT, R.layout.adapter_dialog_photo)); + holderMap.add(new HolderMapEntry(VideoContent.class, PHOTO_CONTENT, R.layout.adapter_dialog_photo)); + holderMap.add(new HolderMapEntry(AnimationContent.class, PHOTO_CONTENT, R.layout.adapter_dialog_photo)); + holderMap.add(new HolderMapEntry(VoiceContent.class, R.layout.adapter_dialog_audio, VOICE_CONTENT)); + holderMap.add(new HolderMapEntry(DocumentContent.class, R.layout.adapter_dialog_doc, DOCUMENT_CONTENT)); + holderMap.add(new HolderMapEntry(ContactContent.class, R.layout.adapter_dialog_contact, CONTACT_CONTENT)); + holderMap.add(new HolderMapEntry(LocationContent.class, R.layout.adapter_dialog_locaton, LOCATION_CONTENT)); + holderMap.add(new HolderMapEntry(StickerContent.class, R.layout.adapter_dialog_sticker, STICKER_CONTENT)); } private static class HolderMapEntry { Class aClass; int id; + int layoutId; - public HolderMapEntry(Class aClass, int id) { + public HolderMapEntry(Class aClass, int id, int layoutId) { this.aClass = aClass; this.id = id; + this.layoutId = layoutId; } public Class getaClass() { diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/XmlBubbleLayouter.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/XmlBubbleLayouter.java new file mode 100644 index 0000000000..45a8411fbd --- /dev/null +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/XmlBubbleLayouter.java @@ -0,0 +1,26 @@ +package im.actor.sdk.controllers.conversation.messages; + +import android.support.annotation.LayoutRes; +import android.view.ViewGroup; + +import org.jetbrains.annotations.NotNull; + +import im.actor.core.entity.Peer; +import im.actor.sdk.controllers.conversation.messages.content.AbsMessageViewHolder; +import im.actor.sdk.util.ViewUtils; + +public class XmlBubbleLayouter extends LambdaBubbleLayouter { + + private int id; + + public XmlBubbleLayouter(@NotNull Matcher matcher, @LayoutRes int id, @NotNull ViewHolderCreator creator) { + super(matcher, creator); + this.id = id; + } + + @Override + public AbsMessageViewHolder onCreateViewHolder(MessagesAdapter adapter, ViewGroup root, Peer peer) { + return creator.onCreateViewHolder(adapter, (ViewGroup) ViewUtils.inflate(id, root), peer); + } + +} diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/util/ViewUtils.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/util/ViewUtils.java index db4a143cb0..f7bd2f200f 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/util/ViewUtils.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/util/ViewUtils.java @@ -21,9 +21,13 @@ public static void goneView(final View view, boolean isAnimated) { } public static View inflate(@LayoutRes int id, ViewGroup viewGroup) { + return inflate(id, viewGroup, false); + } + + public static View inflate(@LayoutRes int id, ViewGroup viewGroup, boolean attach) { return LayoutInflater .from(viewGroup.getContext()) - .inflate(id, viewGroup, false); + .inflate(id, viewGroup, attach); } public static void goneView(final View view, boolean isAnimated, boolean isSlow) { From e75bb8cb6216a1357a2b929c29f86372e875a237 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Fri, 19 Aug 2016 20:38:45 +0300 Subject: [PATCH 291/414] chore(android): CensoredTextHolder example --- .../src/main/java/im/actor/Application.java | 7 +- .../java/im/actor/CensoredTextHolderEx.java | 37 ++++++ .../messages/DefaultLayouter.java | 110 +++++++++++++++++ .../messages/MessagesAdapter.java | 114 ++---------------- .../messages/content/AudioHolder.java | 6 +- .../messages/content/ContactHolder.java | 4 +- .../messages/content/DocHolder.java | 4 +- .../messages/content/LocationHolder.java | 6 +- .../messages/content/PhotoHolder.java | 6 +- .../messages/content/ServiceHolder.java | 4 +- .../messages/content/StickerHolder.java | 6 +- .../messages/content/TextHolder.java | 4 +- .../messages/content/UnsupportedHolder.java | 6 +- 13 files changed, 185 insertions(+), 129 deletions(-) create mode 100644 actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/CensoredTextHolderEx.java create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/DefaultLayouter.java diff --git a/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/Application.java b/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/Application.java index f21c2f1551..11d4d05340 100644 --- a/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/Application.java +++ b/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/Application.java @@ -1,10 +1,7 @@ package im.actor; -import android.content.Intent; import android.graphics.Color; import android.graphics.PorterDuff; -import android.graphics.PorterDuffColorFilter; -import android.net.Uri; import android.os.Bundle; import android.support.multidex.MultiDex; import android.support.v4.app.Fragment; @@ -12,7 +9,6 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.Button; import android.widget.CheckBox; import android.widget.Toast; @@ -26,7 +22,6 @@ import im.actor.core.entity.content.JsonContent; import im.actor.core.entity.content.PhotoContent; import im.actor.develop.R; -import im.actor.holders.BubbleTextHolderLayouter; import im.actor.runtime.json.JSONException; import im.actor.runtime.json.JSONObject; import im.actor.sdk.ActorSDK; @@ -37,6 +32,7 @@ import im.actor.sdk.controllers.conversation.attach.AbsAttachFragment; import im.actor.sdk.controllers.conversation.attach.AttachFragment; import im.actor.sdk.controllers.conversation.messages.BubbleLayouter; +import im.actor.sdk.controllers.conversation.messages.DefaultLayouter; import im.actor.sdk.controllers.conversation.messages.JsonXmlBubbleLayouter; import im.actor.sdk.controllers.conversation.messages.XmlBubbleLayouter; import im.actor.sdk.controllers.conversation.messages.content.PhotoHolder; @@ -109,6 +105,7 @@ private class ActorSDKDelegate extends BaseActorSDKDelegate { @Override public void configureChatViewHolders(ArrayList layouters) { // layouters.add(0, new BubbleTextHolderLayouter()); + layouters.add(0, new DefaultLayouter(DefaultLayouter.TEXT_CONTENT, CensoredTextHolderEx::new)); layouters.add(0, new XmlBubbleLayouter(content -> content instanceof PhotoContent, R.layout.adapter_dialog_photo, (adapter1, root1, peer1) -> new PhotoHolder(adapter1, root1, peer1) { @Override protected void onConfigureViewHolder() { diff --git a/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/CensoredTextHolderEx.java b/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/CensoredTextHolderEx.java new file mode 100644 index 0000000000..d3572719d6 --- /dev/null +++ b/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/CensoredTextHolderEx.java @@ -0,0 +1,37 @@ +package im.actor; + +import android.text.Spannable; +import android.text.TextUtils; +import android.view.View; + +import java.util.ArrayList; + +import im.actor.core.entity.Message; +import im.actor.core.entity.Peer; +import im.actor.sdk.controllers.conversation.messages.MessagesAdapter; +import im.actor.sdk.controllers.conversation.messages.content.TextHolder; + +public class CensoredTextHolderEx extends TextHolder { + public CensoredTextHolderEx(MessagesAdapter adapter, View itemView, Peer peer) { + super(adapter, itemView, peer); + } + + private static ArrayList badWords; + + static { + badWords = new ArrayList<>(); + badWords.add("fuck"); + badWords.add("poke"); + badWords.add("poké"); + } + + @Override + public void bindRawText(CharSequence rawText, long readDate, long receiveDate, Spannable reactions, Message message, boolean isItalic) { + for (String s : badWords) { + rawText = rawText.toString().replaceAll("/*(?i)" + s + "/*", new String(new char[s.length()]).replace('\0', '*')); + } + super.bindRawText(rawText, readDate, receiveDate, reactions, message, isItalic); + } + + +} diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/DefaultLayouter.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/DefaultLayouter.java new file mode 100644 index 0000000000..7047688d3e --- /dev/null +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/DefaultLayouter.java @@ -0,0 +1,110 @@ +package im.actor.sdk.controllers.conversation.messages; + +import android.support.annotation.LayoutRes; +import android.view.View; +import android.view.ViewGroup; + +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; + +import im.actor.core.entity.Peer; +import im.actor.core.entity.content.AbsContent; +import im.actor.core.entity.content.AnimationContent; +import im.actor.core.entity.content.ContactContent; +import im.actor.core.entity.content.DocumentContent; +import im.actor.core.entity.content.LocationContent; +import im.actor.core.entity.content.PhotoContent; +import im.actor.core.entity.content.ServiceContent; +import im.actor.core.entity.content.StickerContent; +import im.actor.core.entity.content.TextContent; +import im.actor.core.entity.content.VideoContent; +import im.actor.core.entity.content.VoiceContent; +import im.actor.sdk.R; +import im.actor.sdk.controllers.conversation.messages.content.AbsMessageViewHolder; +import im.actor.sdk.controllers.conversation.view.BubbleContainer; +import im.actor.sdk.util.ViewUtils; + +public class DefaultLayouter extends LambdaBubbleLayouter { + + public static final int TEXT_CONTENT = 0; + public static final int SERVICE_CONTENT = 1; + public static final int PHOTO_CONTENT = 2; + public static final int VOICE_CONTENT = 4; + public static final int DOCUMENT_CONTENT = 3; + public static final int CONTACT_CONTENT = 5; + public static final int LOCATION_CONTENT = 6; + public static final int STICKER_CONTENT = 7; + + int id; + int layoutId; + + public DefaultLayouter(int id, @NotNull ViewHolderCreator creator) { + super(content -> false, creator); + this.id = id; + } + + + @Override + public boolean isMatch(AbsContent content) { + for (HolderMapEntry e : holderMap) { + if (e.getaClass().isAssignableFrom(content.getClass())) { + layoutId = e.getLayoutId(); + return e.getId() == id; + } + } + return false; + } + + @Override + public AbsMessageViewHolder onCreateViewHolder(MessagesAdapter adapter, ViewGroup root, Peer peer) { + View view = ViewUtils.inflate(layoutId, root); + if (!(view instanceof BubbleContainer)) { + BubbleContainer container = new BubbleContainer(root.getContext()); + container.addView(view); + view = container; + } + return creator.onCreateViewHolder(adapter, (ViewGroup) view, peer); + } + + private static ArrayList holderMap; + + static { + holderMap = new ArrayList<>(); + holderMap.add(new HolderMapEntry(TextContent.class, TEXT_CONTENT, R.layout.adapter_dialog_text)); + holderMap.add(new HolderMapEntry(ServiceContent.class, SERVICE_CONTENT, R.layout.adapter_dialog_service)); + holderMap.add(new HolderMapEntry(PhotoContent.class, PHOTO_CONTENT, R.layout.adapter_dialog_photo)); + holderMap.add(new HolderMapEntry(VideoContent.class, PHOTO_CONTENT, R.layout.adapter_dialog_photo)); + holderMap.add(new HolderMapEntry(AnimationContent.class, PHOTO_CONTENT, R.layout.adapter_dialog_photo)); + holderMap.add(new HolderMapEntry(VoiceContent.class, VOICE_CONTENT, R.layout.adapter_dialog_audio)); + holderMap.add(new HolderMapEntry(DocumentContent.class, DOCUMENT_CONTENT, R.layout.adapter_dialog_doc)); + holderMap.add(new HolderMapEntry(ContactContent.class, CONTACT_CONTENT, R.layout.adapter_dialog_contact)); + holderMap.add(new HolderMapEntry(LocationContent.class, LOCATION_CONTENT, R.layout.adapter_dialog_locaton)); + holderMap.add(new HolderMapEntry(StickerContent.class, STICKER_CONTENT, R.layout.adapter_dialog_locaton)); + } + + private static class HolderMapEntry { + Class aClass; + int id; + int layoutId; + + public HolderMapEntry(Class aClass, int id, int layoutId) { + this.aClass = aClass; + this.id = id; + this.layoutId = layoutId; + } + + public Class getaClass() { + return aClass; + } + + public int getId() { + return id; + } + + public int getLayoutId() { + return layoutId; + } + } + +} \ No newline at end of file diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/MessagesAdapter.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/MessagesAdapter.java index e70148f472..4b3233651a 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/MessagesAdapter.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/MessagesAdapter.java @@ -4,28 +4,16 @@ import android.view.View; import android.view.ViewGroup; -import java.util.ArrayList; import java.util.HashMap; import im.actor.core.entity.Message; import im.actor.core.entity.Peer; import im.actor.core.entity.content.AbsContent; -import im.actor.core.entity.content.AnimationContent; -import im.actor.core.entity.content.ContactContent; -import im.actor.core.entity.content.DocumentContent; -import im.actor.core.entity.content.LocationContent; -import im.actor.core.entity.content.PhotoContent; -import im.actor.core.entity.content.ServiceContent; -import im.actor.core.entity.content.StickerContent; -import im.actor.core.entity.content.TextContent; -import im.actor.core.entity.content.VideoContent; -import im.actor.core.entity.content.VoiceContent; import im.actor.core.viewmodel.ConversationVM; import im.actor.runtime.generic.mvvm.BindedDisplayList; import im.actor.runtime.mvvm.Value; import im.actor.runtime.mvvm.ValueChangedListener; import im.actor.sdk.ActorSDK; -import im.actor.sdk.R; import im.actor.runtime.android.view.BindedListAdapter; import im.actor.sdk.controllers.conversation.messages.content.AudioHolder; import im.actor.sdk.controllers.conversation.messages.content.ContactHolder; @@ -38,26 +26,16 @@ import im.actor.sdk.controllers.conversation.messages.content.StickerHolder; import im.actor.sdk.controllers.conversation.messages.content.TextHolder; import im.actor.sdk.controllers.ActorBinder; -import im.actor.sdk.util.ViewUtils; import static im.actor.sdk.util.ActorSDKMessenger.messenger; public class MessagesAdapter extends BindedListAdapter { - public static final int TEXT_CONTENT = 0; - public static final int SERVICE_CONTENT = 1; - public static final int PHOTO_CONTENT = 2; - public static final int VOICE_CONTENT = 4; - public static final int DOCUMENT_CONTENT = 3; - public static final int CONTACT_CONTENT = 5; - public static final int LOCATION_CONTENT = 6; - public static final int STICKER_CONTENT = 7; - private MessagesFragment messagesFragment; private ActorBinder BINDER = new ActorBinder(); private Context context; - private long firstUnread = -SERVICE_CONTENT; + private long firstUnread = -DefaultLayouter.SERVICE_CONTENT; private long readDate; private long receiveDate; private Peer peer; @@ -65,41 +43,6 @@ public class MessagesAdapter extends BindedListAdapter selected = new HashMap<>(); - private static ArrayList holderMap; - - static { - holderMap = new ArrayList<>(); - holderMap.add(new HolderMapEntry(TextContent.class, TEXT_CONTENT, R.layout.adapter_dialog_text)); - holderMap.add(new HolderMapEntry(ServiceContent.class, SERVICE_CONTENT, R.layout.adapter_dialog_service)); - holderMap.add(new HolderMapEntry(PhotoContent.class, PHOTO_CONTENT, R.layout.adapter_dialog_photo)); - holderMap.add(new HolderMapEntry(VideoContent.class, PHOTO_CONTENT, R.layout.adapter_dialog_photo)); - holderMap.add(new HolderMapEntry(AnimationContent.class, PHOTO_CONTENT, R.layout.adapter_dialog_photo)); - holderMap.add(new HolderMapEntry(VoiceContent.class, R.layout.adapter_dialog_audio, VOICE_CONTENT)); - holderMap.add(new HolderMapEntry(DocumentContent.class, R.layout.adapter_dialog_doc, DOCUMENT_CONTENT)); - holderMap.add(new HolderMapEntry(ContactContent.class, R.layout.adapter_dialog_contact, CONTACT_CONTENT)); - holderMap.add(new HolderMapEntry(LocationContent.class, R.layout.adapter_dialog_locaton, LOCATION_CONTENT)); - holderMap.add(new HolderMapEntry(StickerContent.class, R.layout.adapter_dialog_sticker, STICKER_CONTENT)); - } - - private static class HolderMapEntry { - Class aClass; - int id; - int layoutId; - - public HolderMapEntry(Class aClass, int id, int layoutId) { - this.aClass = aClass; - this.id = id; - this.layoutId = layoutId; - } - - public Class getaClass() { - return aClass; - } - - public int getId() { - return id; - } - } public MessagesAdapter(final BindedDisplayList displayList, @@ -108,14 +51,14 @@ public MessagesAdapter(final BindedDisplayList displayList, matcher = new ViewHolderMatcher(); - matcher.add(new DefaultLayouter(TEXT_CONTENT, R.layout.adapter_dialog_text, TextHolder::new)); - matcher.add(new DefaultLayouter(SERVICE_CONTENT, R.layout.adapter_dialog_service, ServiceHolder::new)); - matcher.add(new DefaultLayouter(PHOTO_CONTENT, R.layout.adapter_dialog_photo, PhotoHolder::new)); - matcher.add(new DefaultLayouter(VOICE_CONTENT, R.layout.adapter_dialog_audio, AudioHolder::new)); - matcher.add(new DefaultLayouter(DOCUMENT_CONTENT, R.layout.adapter_dialog_doc, DocHolder::new)); - matcher.add(new DefaultLayouter(CONTACT_CONTENT, R.layout.adapter_dialog_contact, ContactHolder::new)); - matcher.add(new DefaultLayouter(LOCATION_CONTENT, R.layout.adapter_dialog_locaton, LocationHolder::new)); - matcher.add(new DefaultLayouter(STICKER_CONTENT, R.layout.adapter_dialog_sticker, StickerHolder::new)); + matcher.add(new DefaultLayouter(DefaultLayouter.TEXT_CONTENT, TextHolder::new)); + matcher.add(new DefaultLayouter(DefaultLayouter.SERVICE_CONTENT, ServiceHolder::new)); + matcher.add(new DefaultLayouter(DefaultLayouter.PHOTO_CONTENT, PhotoHolder::new)); + matcher.add(new DefaultLayouter(DefaultLayouter.VOICE_CONTENT, AudioHolder::new)); + matcher.add(new DefaultLayouter(DefaultLayouter.DOCUMENT_CONTENT, DocHolder::new)); + matcher.add(new DefaultLayouter(DefaultLayouter.CONTACT_CONTENT, ContactHolder::new)); + matcher.add(new DefaultLayouter(DefaultLayouter.LOCATION_CONTENT, LocationHolder::new)); + matcher.add(new DefaultLayouter(DefaultLayouter.STICKER_CONTENT, StickerHolder::new)); ActorSDK.sharedActor().getDelegate().configureChatViewHolders(matcher.getLayouters()); @@ -228,11 +171,11 @@ public AbsMessageViewHolder onCreateViewHolder(final ViewGroup viewGroup, int vi public void onBindViewHolder(AbsMessageViewHolder dialogHolder, int index, Message item) { Message prev = null; Message next = null; - if (index > SERVICE_CONTENT) { - next = getItem(index - SERVICE_CONTENT); + if (index > DefaultLayouter.SERVICE_CONTENT) { + next = getItem(index - DefaultLayouter.SERVICE_CONTENT); } - if (index < getItemCount() - SERVICE_CONTENT) { - prev = getItem(index + SERVICE_CONTENT); + if (index < getItemCount() - DefaultLayouter.SERVICE_CONTENT) { + prev = getItem(index + DefaultLayouter.SERVICE_CONTENT); } PreprocessedList list = ((PreprocessedList) getPreprocessedList()); dialogHolder.bindData(item, prev, next, readDate, receiveDate, list.getPreprocessedData()[index]); @@ -247,35 +190,4 @@ public ActorBinder getBinder() { return BINDER; } - private class DefaultLayouter implements BubbleLayouter { - int id; - int layoutId; - HolderCreator holderCreator; - - public DefaultLayouter(int id, int layoutId, HolderCreator holderCreator) { - this.id = id; - this.layoutId = layoutId; - this.holderCreator = holderCreator; - } - - @Override - public boolean isMatch(AbsContent content) { - for (HolderMapEntry e : holderMap) { - if (e.getaClass().isAssignableFrom(content.getClass())) { - return e.getId() == id; - } - } - return false; - } - - @Override - public AbsMessageViewHolder onCreateViewHolder(MessagesAdapter adapter, ViewGroup root, Peer peer) { - return holderCreator.createHolder(adapter, ViewUtils.inflate(layoutId, root), peer); - } - } - - private interface HolderCreator { - AbsMessageViewHolder createHolder(MessagesAdapter adapter, View view, Peer peer); - } - } \ No newline at end of file diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/AudioHolder.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/AudioHolder.java index e23459e075..9798482604 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/AudioHolder.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/AudioHolder.java @@ -71,9 +71,9 @@ public class AudioHolder extends MessageHolder { protected boolean treckingTouch; protected Handler mainThread; - public AudioHolder(MessagesAdapter fragment, final View itemView, Peer peer) { - super(fragment, itemView, false); - context = fragment.getMessagesFragment().getContext(); + public AudioHolder(MessagesAdapter adapter, final View itemView, Peer peer) { + super(adapter, itemView, false); + context = adapter.getMessagesFragment().getContext(); mainThread = new Handler(context.getMainLooper()); waitColor = ActorSDK.sharedActor().style.getConvStatePendingColor(); sentColor = ActorSDK.sharedActor().style.getConvStateSentColor(); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/ContactHolder.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/ContactHolder.java index 5afcff9a27..70059808b3 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/ContactHolder.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/ContactHolder.java @@ -48,8 +48,8 @@ public class ContactHolder extends MessageHolder { private ImageView contactAvatar; - public ContactHolder(MessagesAdapter fragment, final View itemView, Peer peer) { - super(fragment, itemView, false); + public ContactHolder(MessagesAdapter adapter, final View itemView, Peer peer) { + super(adapter, itemView, false); waitColor = ActorSDK.sharedActor().style.getConvStatePendingColor(); sentColor = ActorSDK.sharedActor().style.getConvStateSentColor(); deliveredColor = ActorSDK.sharedActor().style.getConvStateDeliveredColor(); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/DocHolder.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/DocHolder.java index 6f7361795c..75f8de60bf 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/DocHolder.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/DocHolder.java @@ -69,8 +69,8 @@ public class DocHolder extends MessageHolder { protected UploadFileVM uploadFileVM; protected DocumentContent document; - public DocHolder(final MessagesAdapter fragment, View itemView, Peer peer) { - this(fragment, itemView, false, peer); + public DocHolder(final MessagesAdapter adapter, View itemView, Peer peer) { + this(adapter, itemView, false, peer); } public DocHolder(final MessagesAdapter fragment, View itemView, boolean isFullSize, Peer peer) { diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/LocationHolder.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/LocationHolder.java index 9fe43d5b53..6f23e0662e 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/LocationHolder.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/LocationHolder.java @@ -67,9 +67,9 @@ public class LocationHolder extends MessageHolder { protected UploadFileVM uploadFileVM; protected boolean isPhoto; - public LocationHolder(MessagesAdapter fragment, View itemView, Peer peer) { - super(fragment, itemView, false); - this.context = fragment.getMessagesFragment().getActivity(); + public LocationHolder(MessagesAdapter adapter, View itemView, Peer peer) { + super(adapter, itemView, false); + this.context = adapter.getMessagesFragment().getActivity(); COLOR_PENDING = ActorSDK.sharedActor().style.getConvMediaStatePendingColor(); COLOR_SENT = ActorSDK.sharedActor().style.getConvMediaStateSentColor(); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/PhotoHolder.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/PhotoHolder.java index 1d00fd1358..eeeb20c514 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/PhotoHolder.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/PhotoHolder.java @@ -102,9 +102,9 @@ public class PhotoHolder extends MessageHolder { private final ControllerListener animationController; private Animatable anim; - public PhotoHolder(MessagesAdapter fragment, View itemView, Peer peer) { - super(fragment, itemView, false); - this.context = fragment.getMessagesFragment().getActivity(); + public PhotoHolder(MessagesAdapter adapter, View itemView, Peer peer) { + super(adapter, itemView, false); + this.context = adapter.getMessagesFragment().getActivity(); COLOR_PENDING = ActorSDK.sharedActor().style.getConvMediaStatePendingColor(); COLOR_SENT = ActorSDK.sharedActor().style.getConvMediaStateSentColor(); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/ServiceHolder.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/ServiceHolder.java index 9cfe6e91a5..de6c09a1b1 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/ServiceHolder.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/ServiceHolder.java @@ -21,8 +21,8 @@ public class ServiceHolder extends MessageHolder { private TextView messageText; private boolean isChannel; - public ServiceHolder(MessagesAdapter fragment, View itemView, Peer peer) { - super(fragment, itemView, true); + public ServiceHolder(MessagesAdapter adapter, View itemView, Peer peer) { + super(adapter, itemView, true); isChannel = peer.getPeerType() == PeerType.GROUP && groups().get(peer.getPeerId()).getGroupType() == GroupType.CHANNEL; diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/StickerHolder.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/StickerHolder.java index de2f247ef6..d8fa5618d4 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/StickerHolder.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/StickerHolder.java @@ -41,10 +41,10 @@ public class StickerHolder extends MessageHolder { // Content Views private StickerView sticker; - public StickerHolder(MessagesAdapter fragment, View itemView, Peer peer) { + public StickerHolder(MessagesAdapter adapter, View itemView, Peer peer) { - super(fragment, itemView, false); - this.context = fragment.getMessagesFragment().getActivity(); + super(adapter, itemView, false); + this.context = adapter.getMessagesFragment().getActivity(); COLOR_PENDING = ActorSDK.sharedActor().style.getConvMediaStatePendingColor(); COLOR_SENT = ActorSDK.sharedActor().style.getConvMediaStateSentColor(); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/TextHolder.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/TextHolder.java index 77bf45a840..2a459689c0 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/TextHolder.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/TextHolder.java @@ -34,8 +34,8 @@ public class TextHolder extends MessageHolder { private int readColor; private int errorColor; - public TextHolder(MessagesAdapter fragment, final View itemView, Peer peer) { - super(fragment, itemView, false); + public TextHolder(MessagesAdapter adapter, final View itemView, Peer peer) { + super(adapter, itemView, false); mainContainer = (ViewGroup) itemView.findViewById(R.id.mainContainer); messageBubble = (FrameLayout) itemView.findViewById(R.id.fl_bubble); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/UnsupportedHolder.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/UnsupportedHolder.java index 0cd03209ab..cccfb3e4f9 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/UnsupportedHolder.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/UnsupportedHolder.java @@ -16,10 +16,10 @@ public class UnsupportedHolder extends TextHolder { protected String text; - public UnsupportedHolder(MessagesAdapter fragment, View itemView, Peer peer) { - super(fragment, itemView, peer); + public UnsupportedHolder(MessagesAdapter fragmeadaptert, View itemView, Peer peer) { + super(fragmeadaptert, itemView, peer); - text = fragment.getMessagesFragment().getResources().getString(R.string.chat_unsupported); + text = fragmeadaptert.getMessagesFragment().getResources().getString(R.string.chat_unsupported); onConfigureViewHolder(); } From 3b6ca044a295b5c734f89756b107f4a2cee09eec Mon Sep 17 00:00:00 2001 From: kor_ka Date: Fri, 19 Aug 2016 21:28:10 +0300 Subject: [PATCH 292/414] chore(android): CensoredTextHolder example with spannable --- .../java/im/actor/CensoredTextHolderEx.java | 43 ++++++++++++++++++- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/CensoredTextHolderEx.java b/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/CensoredTextHolderEx.java index d3572719d6..bec7683773 100644 --- a/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/CensoredTextHolderEx.java +++ b/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/CensoredTextHolderEx.java @@ -1,15 +1,31 @@ package im.actor; +import android.graphics.Color; +import android.graphics.Paint; import android.text.Spannable; +import android.text.SpannableString; +import android.text.Spanned; +import android.text.TextPaint; import android.text.TextUtils; +import android.text.style.ClickableSpan; +import android.text.style.URLSpan; import android.view.View; import java.util.ArrayList; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import im.actor.core.entity.GroupMember; import im.actor.core.entity.Message; import im.actor.core.entity.Peer; +import im.actor.core.viewmodel.UserVM; +import im.actor.sdk.ActorSDK; import im.actor.sdk.controllers.conversation.messages.MessagesAdapter; import im.actor.sdk.controllers.conversation.messages.content.TextHolder; +import im.actor.sdk.controllers.conversation.view.MentionSpan; +import im.actor.sdk.view.BaseUrlSpan; + +import static im.actor.sdk.util.ActorSDKMessenger.users; public class CensoredTextHolderEx extends TextHolder { public CensoredTextHolderEx(MessagesAdapter adapter, View itemView, Peer peer) { @@ -27,11 +43,34 @@ public CensoredTextHolderEx(MessagesAdapter adapter, View itemView, Peer peer) { @Override public void bindRawText(CharSequence rawText, long readDate, long receiveDate, Spannable reactions, Message message, boolean isItalic) { + Spannable res = new SpannableString(rawText); for (String s : badWords) { - rawText = rawText.toString().replaceAll("/*(?i)" + s + "/*", new String(new char[s.length()]).replace('\0', '*')); +// rawText = rawText.toString().replaceAll("/*(?i)" + s + "/*", new String(new char[s.length()]).replace('\0', '*')); + Pattern p = Pattern.compile("/*(?i)" + s + "/*"); + Matcher m = p.matcher(rawText.toString()); + while (m.find()) { + CensorSpan span = new CensorSpan(); + res.setSpan(span, m.start(), m.end(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } } - super.bindRawText(rawText, readDate, receiveDate, reactions, message, isItalic); + + + super.bindRawText(res, readDate, receiveDate, reactions, message, isItalic); } + private class CensorSpan extends ClickableSpan { + + @Override + public void updateDrawState(TextPaint ds) { + super.updateDrawState(ds); + ds.setColor(Color.BLACK); + ds.bgColor = Color.BLACK; + ds.setUnderlineText(false); + } + + @Override + public void onClick(View view) { + } + } } From 88d23d6954bdda5168e61fd902951b8bf7f5086a Mon Sep 17 00:00:00 2001 From: kor_ka Date: Mon, 22 Aug 2016 16:50:55 +0300 Subject: [PATCH 293/414] chore(android): create channel flow with privacy/add member --- .../compose/GroupNameFragment.java | 13 ++-- .../compose/GroupUsersFragment.java | 60 +++++++++++++++---- .../controllers/group/GroupTypeActivity.java | 2 +- .../controllers/group/GroupTypeFragment.java | 25 ++++++-- .../src/main/res/values-ru/ui_text.xml | 1 + .../src/main/res/values/ui_text.xml | 1 + .../main/java/im/actor/core/Messenger.java | 13 ++++ 7 files changed, 88 insertions(+), 27 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/compose/GroupNameFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/compose/GroupNameFragment.java index 9d8bea9a22..9f3f5a9354 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/compose/GroupNameFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/compose/GroupNameFragment.java @@ -2,9 +2,7 @@ import android.app.Activity; import android.content.Intent; -import android.net.Uri; import android.os.Bundle; -import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -15,14 +13,11 @@ import android.widget.EditText; import android.widget.TextView; -import java.util.List; - -import im.actor.runtime.function.Consumer; import im.actor.sdk.ActorSDK; import im.actor.sdk.R; import im.actor.sdk.controllers.Intents; import im.actor.sdk.controllers.BaseFragment; -import im.actor.sdk.controllers.tools.MediaPickerCallback; +import im.actor.sdk.controllers.group.GroupTypeFragment; import im.actor.sdk.util.Screen; import im.actor.sdk.view.avatar.AvatarView; import im.actor.sdk.util.KeyboardHelper; @@ -137,12 +132,12 @@ private void next() { if (title.length() > 0) { if (isChannel) { execute(messenger().createChannel(groupName.getText().toString().trim(), avatarPath).then(gid -> { - startActivity(Intents.openGroupDialog(gid, false, getActivity())); - getActivity().finish(); + ((CreateGroupActivity) getActivity()).showNextFragment( + GroupTypeFragment.create(gid, true), false); })); } else { ((CreateGroupActivity) getActivity()).showNextFragment( - GroupUsersFragment.create(groupName.getText().toString().trim(), avatarPath), false); + GroupUsersFragment.createGroup(groupName.getText().toString().trim(), avatarPath), false); } } } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/compose/GroupUsersFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/compose/GroupUsersFragment.java index db087812e0..fd23720cfc 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/compose/GroupUsersFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/compose/GroupUsersFragment.java @@ -19,12 +19,14 @@ import im.actor.core.entity.Contact; import im.actor.core.viewmodel.CommandCallback; import im.actor.runtime.function.Consumer; +import im.actor.runtime.promise.Promise; import im.actor.sdk.ActorSDK; import im.actor.sdk.R; import im.actor.sdk.controllers.Intents; import im.actor.sdk.controllers.compose.view.UserSpan; import im.actor.sdk.controllers.contacts.BaseContactFragment; import im.actor.sdk.util.BoxUtil; +import im.actor.sdk.util.KeyboardHelper; import im.actor.sdk.util.Screen; import static im.actor.sdk.util.ActorSDKMessenger.messenger; @@ -36,16 +38,17 @@ public class GroupUsersFragment extends BaseContactFragment { private String avatarPath; private EditText searchField; private TextWatcher textWatcher; + private boolean isChannel; + private int gid; public GroupUsersFragment() { super(true, false, true); setRootFragment(true); setHomeAsUp(true); - setTitle(R.string.create_group_title); } - public static GroupUsersFragment create(String title, String avatarPath) { + public static GroupUsersFragment createGroup(String title, String avatarPath) { GroupUsersFragment res = new GroupUsersFragment(); Bundle args = new Bundle(); args.putString("title", title); @@ -54,9 +57,21 @@ public static GroupUsersFragment create(String title, String avatarPath) { return res; } + public static GroupUsersFragment createChannel(int gid) { + GroupUsersFragment res = new GroupUsersFragment(); + Bundle args = new Bundle(); + args.putBoolean("isChannel", true); + args.putInt("gid", gid); + res.setArguments(args); + return res; + } + @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + isChannel = getArguments().getBoolean("isChannel", false); + setTitle(isChannel ? R.string.channel_add_members : R.string.create_group_title); + gid = getArguments().getInt("gid"); title = getArguments().getString("title"); avatarPath = getArguments().getString("avatarPath"); @@ -87,6 +102,8 @@ public void afterTextChanged(Editable s) { filter(filter); } }; + KeyboardHelper helper = new KeyboardHelper(getActivity()); + helper.setImeVisibility(searchField, false); return res; } @@ -100,26 +117,47 @@ public void onResume() { public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); inflater.inflate(R.menu.create_group, menu); - menu.findItem(R.id.done).setEnabled(getSelectedCount() > 0); + menu.findItem(R.id.done).setEnabled(getSelectedCount() > 0 || isChannel); } @Override public boolean onOptionsItemSelected(MenuItem item) { if (item.getItemId() == R.id.done) { - if (getSelectedCount() > 0) { - execute(messenger().createGroup(title, avatarPath, BoxUtil.unbox(getSelected())).then(gid -> { - getActivity().startActivity(Intents.openGroupDialog(gid, true, getActivity())); - getActivity().finish(); - }).failure(e -> { - Toast.makeText(getActivity(), getString(R.string.toast_unable_create_group), - Toast.LENGTH_LONG).show(); - })); + if (isChannel) { + if (getSelectedCount() > 0) { + Promise invites = null; + for (int uid : getSelected()) { + if (invites == null) { + invites = messenger().inviteMemberPromise(gid, uid); + } else { + invites.chain(o -> messenger().inviteMemberPromise(gid, uid)); + } + } + execute(invites.then(o -> openChannel()), R.string.progress_common); + } else { + openChannel(); + } + } else { + if (getSelectedCount() > 0) { + execute(messenger().createGroup(title, avatarPath, BoxUtil.unbox(getSelected())).then(gid -> { + getActivity().startActivity(Intents.openGroupDialog(gid, true, getActivity())); + getActivity().finish(); + }).failure(e -> { + Toast.makeText(getActivity(), getString(R.string.toast_unable_create_group), + Toast.LENGTH_LONG).show(); + })); + } } return true; } return super.onOptionsItemSelected(item); } + protected void openChannel() { + getActivity().startActivity(Intents.openGroupDialog(gid, true, getActivity())); + getActivity().finish(); + } + @Override public void onItemClicked(Contact contact) { if (isSelected(contact.getUid())) { diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupTypeActivity.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupTypeActivity.java index 73e7173824..03574cc918 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupTypeActivity.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupTypeActivity.java @@ -13,7 +13,7 @@ protected void onCreate(Bundle savedInstanceState) { if (savedInstanceState == null) { showFragment(GroupTypeFragment.create( - getIntent().getIntExtra(Intents.EXTRA_GROUP_ID, 0)), false); + getIntent().getIntExtra(Intents.EXTRA_GROUP_ID, 0), false), false); } } } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupTypeFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupTypeFragment.java index c500c3eea8..8f9337cc89 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupTypeFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupTypeFragment.java @@ -18,14 +18,17 @@ import im.actor.sdk.ActorSDK; import im.actor.sdk.R; import im.actor.sdk.controllers.BaseFragment; +import im.actor.sdk.controllers.compose.CreateGroupActivity; +import im.actor.sdk.controllers.compose.GroupUsersFragment; import static im.actor.sdk.util.ActorSDKMessenger.messenger; public class GroupTypeFragment extends BaseFragment { - public static GroupTypeFragment create(int groupId) { + public static GroupTypeFragment create(int groupId, boolean isCreate) { Bundle bundle = new Bundle(); bundle.putInt("groupId", groupId); + bundle.putBoolean("isCreate", isCreate); GroupTypeFragment editFragment = new GroupTypeFragment(); editFragment.setArguments(bundle); return editFragment; @@ -34,6 +37,7 @@ public static GroupTypeFragment create(int groupId) { private EditText publicShortName; private GroupVM groupVM; private boolean isPublic; + private boolean isCreate; public GroupTypeFragment() { setRootFragment(true); @@ -44,7 +48,7 @@ public GroupTypeFragment() { @Override public void onCreate(Bundle saveInstance) { super.onCreate(saveInstance); - + isCreate = getArguments().getBoolean("isCreate", false); groupVM = messenger().getGroup(getArguments().getInt("groupId")); setTitle(groupVM.getGroupType() == GroupType.CHANNEL ? R.string.channel_title : R.string.group_title); @@ -146,21 +150,21 @@ public boolean onOptionsItemSelected(MenuItem item) { return true; } if (nShortName.equals(groupVM.getShortName().get())) { - finishActivity(); + onEditShortNameSuccess(); return true; } execute(messenger().editGroupShortName(groupVM.getId(), nShortName).then(r -> { - finishActivity(); + onEditShortNameSuccess(); }).failure(e -> { Toast.makeText(getActivity(), R.string.group_edit_change_short_name_error, Toast.LENGTH_SHORT).show(); })); } else { if (groupVM.getShortName().get() == null) { - finishActivity(); + onEditShortNameSuccess(); return true; } else { execute(messenger().editGroupShortName(groupVM.getId(), null).then(r -> { - finishActivity(); + onEditShortNameSuccess(); }).failure(e -> { Toast.makeText(getActivity(), R.string.group_edit_change_short_name_error, Toast.LENGTH_SHORT).show(); })); @@ -170,4 +174,13 @@ public boolean onOptionsItemSelected(MenuItem item) { } return super.onOptionsItemSelected(item); } + + protected void onEditShortNameSuccess() { + if (isCreate) { + ((CreateGroupActivity) getActivity()).showNextFragment( + GroupUsersFragment.createChannel(getArguments().getInt("groupId")), false); + } else { + finishActivity(); + } + } } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-ru/ui_text.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-ru/ui_text.xml index 77841633d7..a3f9cf19cc 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-ru/ui_text.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-ru/ui_text.xml @@ -373,6 +373,7 @@ Добавить в группу Добавить в канал + Добавить в канал Покинуть группу Покинуть канал diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml index c1566bfc22..915e0e48c0 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml @@ -367,6 +367,7 @@ Add a member Add a member + Add members Leave group Leave channel Administration diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java index f9937c9452..de86c8fca4 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java @@ -1629,6 +1629,19 @@ public Command inviteMember(int gid, int uid) { .failure(e -> callback.onError(e)); } + /** + * Adding member to group + * + * @param gid group's id + * @param uid user's id + * @return promise of adding member to group + */ + @NotNull + @ObjectiveCName("inviteMemberPromiseWithGid:withUid:") + public Promise inviteMemberPromise(int gid, int uid) { + return modules.getGroupsModule().addMember(gid, uid); + } + /** * Kick member from group * From 04e8694eee64748ee0832612d3566d0e42122d72 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Mon, 22 Aug 2016 17:32:52 +0300 Subject: [PATCH 294/414] fix(android): isCanEditAdmins binding, remove Screen from ActorStyle --- .../src/main/java/im/actor/sdk/ActorStyle.java | 12 +++++------- .../sdk/controllers/dialogs/BaseDialogFragment.java | 2 +- .../sdk/controllers/group/view/MembersAdapter.java | 2 +- .../main/java/im/actor/core/viewmodel/GroupVM.java | 10 ++++++++++ 4 files changed, 17 insertions(+), 9 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorStyle.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorStyle.java index fac4075596..b774c5cc13 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorStyle.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorStyle.java @@ -2,8 +2,6 @@ import android.graphics.Color; -import im.actor.sdk.util.Screen; - /** * Actor Styling class */ @@ -948,14 +946,14 @@ public int getColorWithFallback(int baseColor, int fallbackColor) { ////////////////////////// // DialogsFragment layout settings - private int dialogsPaddingTop = Screen.dp(8); + private int dialogsPaddingTopDp = 8; - public int getDialogsPaddingTop() { - return dialogsPaddingTop; + public int getDialogsPaddingTopDp() { + return dialogsPaddingTopDp; } - public void setDialogsPaddingTop(int dialogsPaddingTop) { - this.dialogsPaddingTop = dialogsPaddingTop; + public void setDialogsPaddingTopDp(int dialogsPaddingTopDp) { + this.dialogsPaddingTopDp = dialogsPaddingTopDp; } // ContactsFragment layout settings diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/BaseDialogFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/BaseDialogFragment.java index 95ad33e059..d43ceb60e3 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/BaseDialogFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/BaseDialogFragment.java @@ -59,7 +59,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa // Header View header = new View(getActivity()); - header.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ActorSDK.sharedActor().style.getDialogsPaddingTop())); + header.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, Screen.dp(ActorSDK.sharedActor().style.getDialogsPaddingTopDp()))); header.setBackgroundColor(ActorSDK.sharedActor().style.getMainBackgroundColor()); addHeaderView(header); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/view/MembersAdapter.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/view/MembersAdapter.java index 40276b37d4..0585b67404 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/view/MembersAdapter.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/view/MembersAdapter.java @@ -263,7 +263,7 @@ public void onError(Exception e) { .setCanceledOnTouchOutside(true); }); } - if (groupVM.getIsCanEditAdministration().get() && !userVM.isBot()) { + if (groupVM.getIsCanEditAdmins().get() && !userVM.isBot()) { alertListBuilder.addItem(!isAdministrator ? activity.getResources().getString(R.string.group_make_admin) : activity.getResources().getString(R.string.group_revoke_admin), () -> { if (!isAdministrator) { messenger().makeAdmin(groupVM.getId(), userVM.getId()).start(new CommandCallback() { diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java index f8cb56194a..2c9883fba6 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java @@ -476,6 +476,16 @@ public BooleanValueModel getIsCanJoin() { return isCanJoin; } + /** + * Is current user can edit admins + * + * @return is current user can edit admins + */ + @NotNull + public BooleanValueModel getIsCanEditAdmins() { + return isCanEditAdmins; + } + /** * Is group deleted * From 7795a11c8f69c2043467d1f901e7d4fb483c096d Mon Sep 17 00:00:00 2001 From: kor_ka Date: Mon, 22 Aug 2016 22:28:58 +0300 Subject: [PATCH 295/414] fix(core): unstashAll in right order --- .../src/main/java/im/actor/runtime/actors/Actor.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/actors/Actor.java b/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/actors/Actor.java index 5ded2ee503..b1b188d244 100755 --- a/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/actors/Actor.java +++ b/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/actors/Actor.java @@ -93,7 +93,9 @@ public void unstashAll(int index) { if (stashedMessages == null || stashedMessages.size() == 0) { return; } - for (StashedMessage stashedMessage : stashedMessages) { + StashedMessage stashedMessage; + for (int i = stashedMessages.size() - 1; i >= 0; i--) { + stashedMessage = stashedMessages.get(i); self().sendFirst(stashedMessage.getMessage(), stashedMessage.getSender()); } stashedMessages.clear(); From 24b482064a7676db444cb51ed1333291a93f5211 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Tue, 23 Aug 2016 19:06:35 +0300 Subject: [PATCH 296/414] fix(core): enable push handling with auth id check --- .../main/java/im/actor/push/PushReceiver.java | 3 +- .../actor/sdk/receivers/SDKPushReceiver.java | 3 +- .../main/java/im/actor/core/Messenger.java | 7 +++-- .../im/actor/core/modules/api/ApiModule.java | 6 +++- .../core/modules/sequence/SequenceActor.java | 28 +++++++++++------ .../actor/core/modules/sequence/Updates.java | 4 +-- .../java/im/actor/core/network/ActorApi.java | 11 +++++-- .../im/actor/core/network/api/ApiBroker.java | 31 +++++++++++-------- .../actor/core/network/api/ApiBrokerInt.java | 30 ++++++++++++++++++ .../network/api/CheckIsCurrentAuthId.java | 15 +++++++++ .../android/AndroidThreadingProvider.java | 9 +++++- 11 files changed, 113 insertions(+), 34 deletions(-) create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/network/api/ApiBrokerInt.java create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/network/api/CheckIsCurrentAuthId.java diff --git a/actor-sdk/sdk-core-android/android-google-push/src/main/java/im/actor/push/PushReceiver.java b/actor-sdk/sdk-core-android/android-google-push/src/main/java/im/actor/push/PushReceiver.java index a002f0f48c..a6eb644c1a 100644 --- a/actor-sdk/sdk-core-android/android-google-push/src/main/java/im/actor/push/PushReceiver.java +++ b/actor-sdk/sdk-core-android/android-google-push/src/main/java/im/actor/push/PushReceiver.java @@ -25,8 +25,9 @@ public void onReceive(Context context, Intent intent) { ActorSDK.sharedActor().waitForReady(); if (extras.containsKey("seq")) { int seq = Integer.parseInt(extras.getString("seq")); + int authId = Integer.parseInt(extras.getString("authId", "0")); Log.d(TAG, "Push received #" + seq); - ActorSDK.sharedActor().getMessenger().onPushReceived(seq); + ActorSDK.sharedActor().getMessenger().onPushReceived(seq, authId); setResultCode(Activity.RESULT_OK); } else if (extras.containsKey("callId")) { long callId = Long.parseLong(extras.getString("callId")); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/receivers/SDKPushReceiver.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/receivers/SDKPushReceiver.java index 2f7462ce60..9598205974 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/receivers/SDKPushReceiver.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/receivers/SDKPushReceiver.java @@ -16,8 +16,9 @@ public void onPushReceived(String payload) { ActorSDK.sharedActor().waitForReady(); if (data.has("seq")) { int seq = data.getInt("seq"); + int authId = data.optInt("authId"); Log.d("SDKPushReceiver", "Seq Received: " + seq); - ActorSDK.sharedActor().getMessenger().onPushReceived(seq); + ActorSDK.sharedActor().getMessenger().onPushReceived(seq, authId); } else if (data.has("callId")) { Long callId = Long.parseLong(data.getString("callId")); int attempt = 0; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java index de86c8fca4..429574d282 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java @@ -690,12 +690,13 @@ public void onNetworkChanged(@NotNull NetworkState state) { /** * MUST be called when external push received * - * @param seq sequence number of update + * @param seq sequence number of update + * @param authId auth id */ @ObjectiveCName("onPushReceivedWithSeq:") - public void onPushReceived(int seq) { + public void onPushReceived(int seq, long authId) { if (modules.getUpdatesModule() != null) { - modules.getUpdatesModule().onPushReceived(seq); + modules.getUpdatesModule().onPushReceived(seq, authId); } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/api/ApiModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/api/ApiModule.java index c233afe035..0890f8bd12 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/api/ApiModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/api/ApiModule.java @@ -12,13 +12,13 @@ import im.actor.core.network.AuthKeyStorage; import im.actor.core.network.Endpoints; import im.actor.core.network.TrustedKey; -import im.actor.core.network.api.ApiBroker; import im.actor.core.network.parser.Request; import im.actor.runtime.actors.ActorRef; import im.actor.runtime.eventbus.BusSubscriber; import im.actor.runtime.eventbus.Event; import im.actor.runtime.mtproto.ConnectionEndpoint; import im.actor.runtime.mtproto.ConnectionEndpointArray; +import im.actor.runtime.promise.Promise; import static im.actor.runtime.actors.ActorSystem.system; @@ -124,6 +124,10 @@ public synchronized void resetToDefaultEndpoints() { actorApi.resetToDefaultEndpoints(); } + public Promise checkIsCurrentAuthId(long authId) { + return actorApi.checkIsCurrentAuthId(authId); + } + @Override public void onBusEvent(Event event) { if (event instanceof AppVisibleChanged) { diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/sequence/SequenceActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/sequence/SequenceActor.java index 46226268e5..6e68474160 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/sequence/SequenceActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/sequence/SequenceActor.java @@ -101,15 +101,21 @@ private void onWeakUpdateReceived(int type, byte[] body, long date) { handler.onWeakUpdate(update, date); } - private void onPushSeqReceived(int seq) { - if (PROCESS_EXTERNAL_PUSH_SEQ) { - if (seq <= this.seq) { - Log.d(TAG, "Ignored PushSeq {seq:" + seq + "}"); - } else { - Log.w(TAG, "External Out of sequence: starting timer for invalidation"); - startInvalidationTimer(); - } + private void onPushSeqReceived(int seq, long authId) { + if (context().getApiModule() == null) { + return; } + context().getApiModule().checkIsCurrentAuthId(authId).then(same -> { + if (same) { + if (seq <= this.seq) { + Log.d(TAG, "Ignored PushSeq {seq:" + seq + "}"); + } else { + Log.w(TAG, "External Out of sequence: starting timer for invalidation"); + startInvalidationTimer(); + } + } + }); + } @Deprecated @@ -416,7 +422,7 @@ public void onReceive(Object message) { stash(); return; } - onPushSeqReceived(((PushSeq) message).seq); + onPushSeqReceived(((PushSeq) message).seq, ((PushSeq) message).authId); } else if (message instanceof WeakUpdate) { WeakUpdate weakUpdate = (WeakUpdate) message; onWeakUpdateReceived(weakUpdate.getUpdateHeader(), weakUpdate.getUpdate(), @@ -435,9 +441,11 @@ public static class Invalidate { } public static class PushSeq { + private long authId; private int seq; - public PushSeq(int seq) { + public PushSeq(int seq, long authId) { + this.authId = authId; this.seq = seq; } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/sequence/Updates.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/sequence/Updates.java index 22d0c8843d..e2157d8ee2 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/sequence/Updates.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/sequence/Updates.java @@ -59,9 +59,9 @@ public SequenceHandlerInt getUpdateHandler() { return updateHandlerInt; } - public void onPushReceived(int seq) { + public void onPushReceived(int seq, long authId) { if (updateActor != null) { - updateActor.send(new SequenceActor.PushSeq(seq)); + updateActor.send(new SequenceActor.PushSeq(seq, authId)); } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/network/ActorApi.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/network/ActorApi.java index a5401af5fa..5454113a57 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/network/ActorApi.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/network/ActorApi.java @@ -5,11 +5,12 @@ package im.actor.core.network; import im.actor.core.api.ApiVersion; -import im.actor.core.network.parser.ApiParserConfig; +import im.actor.core.network.api.ApiBrokerInt; import im.actor.runtime.actors.ActorRef; import im.actor.core.network.api.ApiBroker; import im.actor.core.network.parser.Request; import im.actor.core.network.parser.Response; +import im.actor.runtime.promise.Promise; import im.actor.runtime.threading.AtomicIntegerCompat; import im.actor.runtime.threading.AtomicLongCompat; @@ -35,6 +36,7 @@ public class ActorApi { private final int maxFailureCount; private ActorRef apiBroker; + private ApiBrokerInt apiBrokerInt; /** * Create API @@ -55,8 +57,9 @@ public ActorApi(Endpoints endpoints, AuthKeyStorage keyStorage, ActorApiCallback this.minDelay = minDelay; this.maxDelay = maxDelay; this.maxFailureCount = maxFailureCount; - this.apiBroker = ApiBroker.get(endpoints, keyStorage, callback, isEnableLog, + this.apiBrokerInt = ApiBroker.get(endpoints, keyStorage, callback, isEnableLog, NEXT_ID.get(), minDelay, maxDelay, maxFailureCount); + this.apiBroker = apiBrokerInt.getDest(); } /** @@ -134,4 +137,8 @@ public synchronized void resetToDefaultEndpoints() { public AuthKeyStorage getKeyStorage() { return keyStorage; } + + public Promise checkIsCurrentAuthId(long authId) { + return apiBrokerInt.checkIsCurrentAuthId(authId); + } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/network/api/ApiBroker.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/network/api/ApiBroker.java index 8e491209cd..331028e6e5 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/network/api/ApiBroker.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/network/api/ApiBroker.java @@ -14,10 +14,8 @@ import im.actor.core.network.parser.ParsingExtension; import im.actor.runtime.*; import im.actor.runtime.Runtime; -import im.actor.runtime.actors.Actor; import im.actor.runtime.actors.ActorRef; -import im.actor.runtime.actors.ActorSystem; -import im.actor.runtime.actors.Props; +import im.actor.runtime.actors.AskcableActor; import im.actor.core.util.RandomUtils; import im.actor.core.network.mtp.MTProto; import im.actor.core.network.mtp.MTProtoCallback; @@ -32,23 +30,18 @@ import im.actor.core.network.parser.Request; import im.actor.core.network.parser.Response; import im.actor.core.network.parser.RpcScope; +import im.actor.runtime.promise.Promise; import im.actor.runtime.threading.AtomicIntegerCompat; import im.actor.runtime.threading.CommonTimer; -public class ApiBroker extends Actor { +public class ApiBroker extends AskcableActor { - public static ActorRef get(final Endpoints endpoints, final AuthKeyStorage keyStorage, final ActorApiCallback callback, + public static ApiBrokerInt get(final Endpoints endpoints, final AuthKeyStorage keyStorage, final ActorApiCallback callback, final boolean isEnableLog, int id, final int minDelay, final int maxDelay, final int maxFailureCount) { - return ActorSystem.system().actorOf(Props.create(() -> - new ApiBroker(endpoints, - keyStorage, - callback, - isEnableLog, - minDelay, - maxDelay, - maxFailureCount)), "api/broker#" + id); + + return new ApiBrokerInt(endpoints, keyStorage, callback, isEnableLog, id, minDelay, maxDelay, maxFailureCount); } private static final String TAG = "ApiBroker"; @@ -381,6 +374,10 @@ void connectionCountChanged(int count) { callback.onConnectionsChanged(count); } + private Promise checkIsCurrentAuthId(long authId) { + return new Promise<>(resolver -> resolver.result(authId == currentAuthId)); + } + public static class PerformRequest { private Request message; @@ -645,6 +642,14 @@ public void onConnectionsCountChanged(int count) { } } + @Override + public Promise onAsk(Object message) throws Exception { + if (message instanceof CheckIsCurrentAuthId) { + return checkIsCurrentAuthId(((CheckIsCurrentAuthId) message).getAuthId()); + } + return super.onAsk(message); + } + @Override public void onReceive(Object message) { if (message instanceof InitMTProto) { diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/network/api/ApiBrokerInt.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/network/api/ApiBrokerInt.java new file mode 100644 index 0000000000..b7f9de27ac --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/network/api/ApiBrokerInt.java @@ -0,0 +1,30 @@ +package im.actor.core.network.api; + +import im.actor.core.network.ActorApiCallback; +import im.actor.core.network.AuthKeyStorage; +import im.actor.core.network.Endpoints; +import im.actor.runtime.actors.ActorInterface; +import im.actor.runtime.promise.Promise; + +import static im.actor.runtime.actors.ActorSystem.system; + +public class ApiBrokerInt extends ActorInterface { + public ApiBrokerInt(final Endpoints endpoints, final AuthKeyStorage keyStorage, final ActorApiCallback callback, + final boolean isEnableLog, int id, final int minDelay, + final int maxDelay, + final int maxFailureCount) { + setDest(system().actorOf("api/broker#" + id, () -> new ApiBroker(endpoints, + keyStorage, + callback, + isEnableLog, + minDelay, + maxDelay, + maxFailureCount))); + } + + public Promise checkIsCurrentAuthId(long authId) { + return ask(new CheckIsCurrentAuthId(authId)); + } + + +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/network/api/CheckIsCurrentAuthId.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/network/api/CheckIsCurrentAuthId.java new file mode 100644 index 0000000000..960cfae528 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/network/api/CheckIsCurrentAuthId.java @@ -0,0 +1,15 @@ +package im.actor.core.network.api; + +import im.actor.runtime.actors.ask.AskMessage; + +public class CheckIsCurrentAuthId implements AskMessage { + private long authId; + + public CheckIsCurrentAuthId(long authId) { + this.authId = authId; + } + + public long getAuthId() { + return authId; + } +} diff --git a/actor-sdk/sdk-core/runtime/runtime-android/src/main/java/im/actor/runtime/android/AndroidThreadingProvider.java b/actor-sdk/sdk-core/runtime/runtime-android/src/main/java/im/actor/runtime/android/AndroidThreadingProvider.java index 4178095469..fb71bff927 100644 --- a/actor-sdk/sdk-core/runtime/runtime-android/src/main/java/im/actor/runtime/android/AndroidThreadingProvider.java +++ b/actor-sdk/sdk-core/runtime/runtime-android/src/main/java/im/actor/runtime/android/AndroidThreadingProvider.java @@ -10,6 +10,7 @@ import android.content.IntentFilter; import android.content.SharedPreferences; +import im.actor.core.util.ExponentialBackoff; import im.actor.runtime.Runtime; import im.actor.runtime.actors.ThreadPriority; import im.actor.runtime.android.threading.AndroidDispatcher; @@ -57,8 +58,14 @@ private void invalidateSync() { @Override public void run() { SntpClient client = new SntpClient(); + ExponentialBackoff exponentialBackoff = new ExponentialBackoff(); while (!client.requestTime(serverHost, 10000)) { - + exponentialBackoff.onFailure(); + try { + Thread.sleep(exponentialBackoff.exponentialWait()); + } catch (InterruptedException e) { + e.printStackTrace(); + } } syncDelta = client.getClockOffset(); preference.edit().putLong("delta", syncDelta).commit(); From 5b497fe59f6d081345c1ffed0e9f4a9220c849f8 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Wed, 24 Aug 2016 14:21:16 +0300 Subject: [PATCH 297/414] fix(android): mutate drawables before tinting --- .../src/main/java/im/actor/sdk/controllers/BaseFragment.java | 3 +++ .../im/actor/sdk/controllers/profile/ProfileFragment.java | 5 +++++ .../sdk/controllers/settings/BaseActorSettingsFragment.java | 3 +++ 3 files changed, 11 insertions(+) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/BaseFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/BaseFragment.java index 068c790f17..c9ba4d86bb 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/BaseFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/BaseFragment.java @@ -341,6 +341,7 @@ public View buildRecord(String titleText, String valueText, int resourceId, bool if (resourceId != 0 && showIcon) { ImageView iconView = (ImageView) recordView.findViewById(R.id.recordIcon); Drawable drawable = DrawableCompat.wrap(getResources().getDrawable(resourceId)); + drawable.mutate(); DrawableCompat.setTint(drawable, style.getSettingsIconColor()); iconView.setImageDrawable(drawable); } @@ -365,6 +366,7 @@ public View buildRecordBig(String valueText, int resourceId, boolean showIcon, b if (resourceId != 0 && showIcon) { ImageView iconView = (ImageView) recordView.findViewById(R.id.recordIcon); Drawable drawable = DrawableCompat.wrap(getResources().getDrawable(resourceId)); + drawable.mutate(); DrawableCompat.setTint(drawable, style.getSettingsIconColor()); iconView.setImageDrawable(drawable); } @@ -389,6 +391,7 @@ public View buildRecordAction(String valueText, int resourceId, boolean showIcon if (resourceId != 0 && showIcon) { ImageView iconView = (ImageView) recordView.findViewById(R.id.recordIcon); Drawable drawable = DrawableCompat.wrap(getResources().getDrawable(resourceId)); + drawable.mutate(); DrawableCompat.setTint(drawable, style.getGroupActionAddIconColor()); iconView.setImageDrawable(drawable); } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/profile/ProfileFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/profile/ProfileFragment.java index b69fe6d2be..231873311b 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/profile/ProfileFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/profile/ProfileFragment.java @@ -146,6 +146,7 @@ public View onCreateView(final LayoutInflater inflater, ViewGroup container, Bun addContactTitle.setText(getString(R.string.profile_contacts_added)); addContactTitle.setTextColor(style.getProfileContactIconColor()); Drawable drawable = DrawableCompat.wrap(getResources().getDrawable(R.drawable.ic_check_circle_black_24dp)); + drawable.mutate(); DrawableCompat.setTint(drawable, style.getProfileContactIconColor()); addContactIcon.setImageDrawable(drawable); addContact.setOnClickListener(v -> { @@ -155,6 +156,7 @@ public View onCreateView(final LayoutInflater inflater, ViewGroup container, Bun addContactTitle.setText(getString(R.string.profile_contacts_available)); addContactTitle.setTextColor(style.getProfileContactIconColor()); Drawable drawable = DrawableCompat.wrap(getResources().getDrawable(R.drawable.ic_person_add_white_24dp)); + drawable = drawable.mutate(); DrawableCompat.setTint(drawable, style.getProfileContactIconColor()); addContactIcon.setImageDrawable(drawable); addContact.setOnClickListener(v -> { @@ -173,6 +175,7 @@ public View onCreateView(final LayoutInflater inflater, ViewGroup container, Bun TextView newMessageTitle = (TextView) newMessageView.findViewById(R.id.newMessageText); { Drawable drawable = DrawableCompat.wrap(getResources().getDrawable(R.drawable.ic_chat_black_24dp)); + drawable.mutate(); DrawableCompat.setTint(drawable, style.getListActionColor()); newMessageIcon.setImageDrawable(drawable); newMessageTitle.setTextColor(style.getListActionColor()); @@ -435,6 +438,7 @@ public void onChanged(final String newUserName, Value valueModel) { notificationContainer.setOnClickListener(v -> notificationEnable.setChecked(!notificationEnable.isChecked())); ImageView iconView = (ImageView) res.findViewById(R.id.settings_notification_icon); Drawable drawable = DrawableCompat.wrap(getResources().getDrawable(R.drawable.ic_list_black_24dp)); + drawable.mutate(); DrawableCompat.setTint(drawable, style.getSettingsIconColor()); iconView.setImageDrawable(drawable); @@ -495,6 +499,7 @@ public void onChanged(final String newUserName, Value valueModel) { }); ImageView blockIconView = (ImageView) res.findViewById(R.id.settings_block_icon); Drawable blockDrawable = DrawableCompat.wrap(getResources().getDrawable(R.drawable.ic_block_white_24dp)); + drawable.mutate(); DrawableCompat.setTint(blockDrawable, style.getSettingsIconColor()); blockIconView.setImageDrawable(blockDrawable); } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/BaseActorSettingsFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/BaseActorSettingsFragment.java index e75a21bff1..f91fb02fb5 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/BaseActorSettingsFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/BaseActorSettingsFragment.java @@ -137,6 +137,7 @@ public void onChanged(final String val, Value Value) { final View recordView = inflater.inflate(R.layout.contact_record, nickContainer, false); ImageView nickIcon = (ImageView) recordView.findViewById(R.id.recordIcon); Drawable drawable = DrawableCompat.wrap(getResources().getDrawable(R.drawable.ic_mention_24_dp)); + drawable.mutate(); DrawableCompat.setTint(drawable, style.getSettingsIconColor()); nickIcon.setImageDrawable(drawable); @@ -165,6 +166,7 @@ public void onClick(View v) { final TextView aboutTitle = (TextView) about.findViewById(R.id.value); ImageView nickIcon = (ImageView) about.findViewById(R.id.recordIcon); Drawable drawable = DrawableCompat.wrap(getResources().getDrawable(R.drawable.ic_info_black_24dp)); + drawable.mutate(); DrawableCompat.setTint(drawable, style.getSettingsIconColor()); nickIcon.setImageDrawable(drawable); aboutTitle.setTextColor(style.getTextPrimaryColor()); @@ -301,6 +303,7 @@ public void onChanged(ArrayListUserEmail val, Value Value) { ImageView tintImageView = (ImageView) recordView.findViewById(R.id.recordIcon); if (i == 0) { Drawable drawable = DrawableCompat.wrap(getResources().getDrawable(R.drawable.ic_email_white_24dp)); + drawable.mutate(); DrawableCompat.setTint(drawable, style.getSettingsIconColor()); tintImageView.setImageDrawable(drawable); } else { From 2a3e91c691e27a7eb84065c4b4e8e2b9fa614f4a Mon Sep 17 00:00:00 2001 From: kor_ka Date: Wed, 24 Aug 2016 16:08:55 +0300 Subject: [PATCH 298/414] chore(android): bump gradle --- actor-sdk/sdk-core-android/android-app/build.gradle | 2 +- actor-sdk/sdk-core-android/android-google-maps/build.gradle | 2 +- actor-sdk/sdk-core-android/android-google-push/build.gradle | 2 +- .../src/main/java/im/actor/push/PushManager.java | 2 +- actor-sdk/sdk-core-android/android-sdk/build.gradle | 2 +- .../src/main/java/im/actor/sdk/push/ActorPushService.java | 2 +- .../android-sdk/src/main/res/layout/fragment_profile.xml | 1 + actor-sdk/sdk-core/core/core-android/build.gradle | 2 +- .../src/main/java/im/actor/core/network/api/AuthKeyActor.java | 2 +- .../java/im/actor/core/network/mtp/actors/ManagerActor.java | 2 +- actor-sdk/sdk-core/runtime/runtime-android/build.gradle | 2 +- .../im/actor/runtime/android/AndroidThreadingProvider.java | 2 +- .../main/java/im/actor/runtime}/util/ExponentialBackoff.java | 2 +- gradle/wrapper/gradle-wrapper.properties | 4 ++-- 14 files changed, 15 insertions(+), 14 deletions(-) rename actor-sdk/sdk-core/{core/core-shared/src/main/java/im/actor/core => runtime/runtime-shared/src/main/java/im/actor/runtime}/util/ExponentialBackoff.java (98%) diff --git a/actor-sdk/sdk-core-android/android-app/build.gradle b/actor-sdk/sdk-core-android/android-app/build.gradle index 7a49b9cbef..c013ad78f6 100644 --- a/actor-sdk/sdk-core-android/android-app/build.gradle +++ b/actor-sdk/sdk-core-android/android-app/build.gradle @@ -1,6 +1,6 @@ buildscript { dependencies { - classpath 'com.android.tools.build:gradle:2.1.2' + classpath 'com.android.tools.build:gradle:2.1.3' classpath 'me.tatarka:gradle-retrolambda:3.2.5' } } diff --git a/actor-sdk/sdk-core-android/android-google-maps/build.gradle b/actor-sdk/sdk-core-android/android-google-maps/build.gradle index e25667ecd2..88ae171a0a 100644 --- a/actor-sdk/sdk-core-android/android-google-maps/build.gradle +++ b/actor-sdk/sdk-core-android/android-google-maps/build.gradle @@ -1,6 +1,6 @@ buildscript { dependencies { - classpath 'com.android.tools.build:gradle:2.1.2' + classpath 'com.android.tools.build:gradle:2.1.3' classpath 'com.github.dcendents:android-maven-plugin:1.2' classpath "io.codearte.gradle.nexus:gradle-nexus-staging-plugin:0.5.3" classpath 'me.tatarka:gradle-retrolambda:3.2.5' diff --git a/actor-sdk/sdk-core-android/android-google-push/build.gradle b/actor-sdk/sdk-core-android/android-google-push/build.gradle index 7e6e58cf81..499980ac4a 100644 --- a/actor-sdk/sdk-core-android/android-google-push/build.gradle +++ b/actor-sdk/sdk-core-android/android-google-push/build.gradle @@ -1,6 +1,6 @@ buildscript { dependencies { - classpath 'com.android.tools.build:gradle:2.1.2' + classpath 'com.android.tools.build:gradle:2.1.3' classpath 'com.github.dcendents:android-maven-plugin:1.2' classpath "io.codearte.gradle.nexus:gradle-nexus-staging-plugin:0.5.3" classpath 'me.tatarka:gradle-retrolambda:3.2.5' diff --git a/actor-sdk/sdk-core-android/android-google-push/src/main/java/im/actor/push/PushManager.java b/actor-sdk/sdk-core-android/android-google-push/src/main/java/im/actor/push/PushManager.java index 0a7673f491..ad1c5637ec 100644 --- a/actor-sdk/sdk-core-android/android-google-push/src/main/java/im/actor/push/PushManager.java +++ b/actor-sdk/sdk-core-android/android-google-push/src/main/java/im/actor/push/PushManager.java @@ -6,7 +6,7 @@ import java.io.IOException; -import im.actor.core.util.ExponentialBackoff; +import im.actor.runtime.util.ExponentialBackoff; import im.actor.runtime.Log; import im.actor.sdk.ActorSDK; import im.actor.sdk.core.ActorPushManager; diff --git a/actor-sdk/sdk-core-android/android-sdk/build.gradle b/actor-sdk/sdk-core-android/android-sdk/build.gradle index 270da1981f..787550856c 100644 --- a/actor-sdk/sdk-core-android/android-sdk/build.gradle +++ b/actor-sdk/sdk-core-android/android-sdk/build.gradle @@ -1,6 +1,6 @@ buildscript { dependencies { - classpath 'com.android.tools.build:gradle:2.1.2' + classpath 'com.android.tools.build:gradle:2.1.3' classpath 'com.github.dcendents:android-maven-plugin:1.2' classpath "io.codearte.gradle.nexus:gradle-nexus-staging-plugin:0.5.3" classpath 'me.tatarka:gradle-retrolambda:3.2.5' diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/push/ActorPushService.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/push/ActorPushService.java index cc9d9d9a54..9422256a57 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/push/ActorPushService.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/push/ActorPushService.java @@ -22,7 +22,7 @@ import java.util.concurrent.Executor; import java.util.concurrent.Executors; -import im.actor.core.util.ExponentialBackoff; +import im.actor.runtime.util.ExponentialBackoff; /** * Actor Push service based on MQTT diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_profile.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_profile.xml index f6fa6b17eb..cc9024150d 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_profile.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_profile.xml @@ -10,6 +10,7 @@ android:layout_height="match_parent"> */ -package im.actor.core.util; +package im.actor.runtime.util; import java.util.Random; diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 1d10ddc013..b504ce79f6 100755 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Sat Apr 09 16:04:30 MSK 2016 +#Wed Aug 24 15:48:51 MSK 2016 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip From e0320d5c18bf4920b8dfecf3552e3fcc6794d2ba Mon Sep 17 00:00:00 2001 From: kor_ka Date: Wed, 24 Aug 2016 20:15:19 +0300 Subject: [PATCH 299/414] style(android): profile redesign --- .../controllers/profile/ProfileFragment.java | 79 +++- .../src/main/res/layout/fragment_profile.xml | 426 ++++++++++-------- .../src/main/res/values-ru/ui_text.xml | 1 + .../src/main/res/values/ui_text.xml | 1 + 4 files changed, 306 insertions(+), 201 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/profile/ProfileFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/profile/ProfileFragment.java index 231873311b..f78d237b59 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/profile/ProfileFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/profile/ProfileFragment.java @@ -5,6 +5,7 @@ import android.content.ClipboardManager; import android.content.Context; import android.content.Intent; +import android.content.res.ColorStateList; import android.graphics.Color; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; @@ -12,12 +13,14 @@ import android.net.Uri; import android.os.Bundle; import android.provider.Settings; +import android.support.design.widget.FloatingActionButton; import android.support.design.widget.Snackbar; import android.support.v4.graphics.drawable.DrawableCompat; import android.support.v7.app.ActionBar; import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.SwitchCompat; +import android.util.StateSet; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -45,6 +48,7 @@ import im.actor.sdk.R; import im.actor.sdk.controllers.BaseFragment; import im.actor.sdk.controllers.Intents; +import im.actor.sdk.controllers.compose.ComposeActivity; import im.actor.sdk.controllers.fragment.preview.ViewAvatarActivity; import im.actor.sdk.util.Screen; import im.actor.sdk.util.ViewUtils; @@ -102,6 +106,7 @@ public View onCreateView(final LayoutInflater inflater, ViewGroup container, Bun res.findViewById(R.id.container).setBackgroundColor(style.getMainBackgroundColor()); res.findViewById(R.id.avatarContainer).setBackgroundColor(style.getToolBarColor()); + res.findViewById(R.id.after_actions_divider).setBackgroundColor(style.getBackyardBackgroundColor()); // @@ -133,6 +138,22 @@ public View onCreateView(final LayoutInflater inflater, ViewGroup container, Bun lastSeen.setTextColor(style.getProfileSubtitleColor()); bind(lastSeen, user); + // + // Fab + // + + FloatingActionButton fab = (FloatingActionButton) res.findViewById(R.id.fab); + + fab.setBackgroundTintList(new ColorStateList(new int[][]{ + new int[]{android.R.attr.state_pressed}, + StateSet.WILD_CARD, + + }, new int[]{ + ActorSDK.sharedActor().style.getFabPressedColor(), + ActorSDK.sharedActor().style.getFabColor(), + })); + fab.setRippleColor(ActorSDK.sharedActor().style.getFabPressedColor()); + fab.setOnClickListener(v -> startActivity(new Intent(getActivity(), ComposeActivity.class))); // // Add/Remove Contact @@ -143,6 +164,7 @@ public View onCreateView(final LayoutInflater inflater, ViewGroup container, Bun final TextView addContactTitle = (TextView) addContact.findViewById(R.id.addContactTitle); bind(user.isContact(), (isContact, valueModel) -> { if (isContact) { + //add to contacts btn addContactTitle.setText(getString(R.string.profile_contacts_added)); addContactTitle.setTextColor(style.getProfileContactIconColor()); Drawable drawable = DrawableCompat.wrap(getResources().getDrawable(R.drawable.ic_check_circle_black_24dp)); @@ -152,7 +174,12 @@ public View onCreateView(final LayoutInflater inflater, ViewGroup container, Bun addContact.setOnClickListener(v -> { execute(ActorSDK.sharedActor().getMessenger().removeContact(user.getId())); }); + + //fab + fab.setImageResource(R.drawable.ic_message_white_24dp); + fab.setOnClickListener(view -> startActivity(Intents.openPrivateDialog(user.getId(), true, getActivity()))); } else { + //add to contacts btn addContactTitle.setText(getString(R.string.profile_contacts_available)); addContactTitle.setTextColor(style.getProfileContactIconColor()); Drawable drawable = DrawableCompat.wrap(getResources().getDrawable(R.drawable.ic_person_add_white_24dp)); @@ -162,6 +189,10 @@ public View onCreateView(final LayoutInflater inflater, ViewGroup container, Bun addContact.setOnClickListener(v -> { execute(ActorSDK.sharedActor().getMessenger().addContact(user.getId())); }); + + //fab + fab.setImageResource(R.drawable.ic_person_add_white_24dp); + fab.setOnClickListener(view -> execute(ActorSDK.sharedActor().getMessenger().addContact(user.getId()))); } }); @@ -190,30 +221,44 @@ public View onCreateView(final LayoutInflater inflater, ViewGroup container, Bun // View voiceCallView = res.findViewById(R.id.voiceCall); - if (ActorSDK.sharedActor().isCallsEnabled()) { + if (ActorSDK.sharedActor().isCallsEnabled() && !user.isBot()) { ImageView voiceViewIcon = (ImageView) voiceCallView.findViewById(R.id.actionIcon); TextView voiceViewTitle = (TextView) voiceCallView.findViewById(R.id.actionText); - if (!user.isBot()) { - Drawable drawable = DrawableCompat.wrap(getResources().getDrawable(R.drawable.ic_phone_white_24dp)); - drawable = drawable.mutate(); - DrawableCompat.setTint(drawable, style.getListActionColor()); - voiceViewIcon.setImageDrawable(drawable); - voiceViewTitle.setTextColor(style.getListActionColor()); + Drawable drawable = DrawableCompat.wrap(getResources().getDrawable(R.drawable.ic_phone_white_24dp)); + drawable = drawable.mutate(); + DrawableCompat.setTint(drawable, style.getListActionColor()); + voiceViewIcon.setImageDrawable(drawable); + voiceViewTitle.setTextColor(style.getListActionColor()); - voiceCallView.setOnClickListener(v -> { - execute(ActorSDK.sharedActor().getMessenger().doCall(user.getId())); - }); - } else { - Drawable drawable = DrawableCompat.wrap(getResources().getDrawable(R.drawable.ic_phone_white_24dp)); - drawable = drawable.mutate(); - DrawableCompat.setTint(drawable, style.getTextHintColor()); - voiceViewIcon.setImageDrawable(drawable); - voiceViewTitle.setTextColor(style.getTextHintColor()); - } + voiceCallView.setOnClickListener(v -> { + execute(ActorSDK.sharedActor().getMessenger().doCall(user.getId())); + }); } else { voiceCallView.setVisibility(View.GONE); } + // + // Video Call + // + + + View videoCallView = res.findViewById(R.id.videoCall); + if (ActorSDK.sharedActor().isCallsEnabled() && !user.isBot()) { + ImageView voiceViewIcon = (ImageView) videoCallView.findViewById(R.id.videoCallIcon); + TextView voiceViewTitle = (TextView) videoCallView.findViewById(R.id.videoCallText); + Drawable drawable = DrawableCompat.wrap(getResources().getDrawable(R.drawable.ic_videocam_white_24dp)); + drawable = drawable.mutate(); + DrawableCompat.setTint(drawable, style.getListActionColor()); + voiceViewIcon.setImageDrawable(drawable); + voiceViewTitle.setTextColor(style.getListActionColor()); + + videoCallView.setOnClickListener(v -> { + execute(ActorSDK.sharedActor().getMessenger().doVideoCall(user.getId())); + }); + } else { + videoCallView.setVisibility(View.GONE); + } + // // Contact Information diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_profile.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_profile.xml index cc9024150d..80b6d05018 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_profile.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_profile.xml @@ -9,239 +9,297 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - - + android:layout_height="wrap_content"> - - - - - - + + + android:layout_height="148dp" + android:layout_gravity="bottom" + android:gravity="center_vertical" + android:paddingTop="44dp"> - + - + + + + + + + + android:gravity="center_vertical" + android:paddingRight="8dp"> + android:layout_width="24dp" + android:layout_height="24dp" + android:layout_marginLeft="16dp" + android:layout_marginRight="32dp" + android:scaleType="center" /> + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="1" + android:gravity="center_vertical" + android:textSize="16sp" /> - + android:gravity="center_vertical" + android:paddingRight="8dp"> + android:layout_width="24dp" + android:layout_height="24dp" + android:layout_marginLeft="16dp" + android:layout_marginRight="32dp" + android:scaleType="center" /> + android:textSize="16sp" /> + + + + + + + + + + android:gravity="center_vertical" + android:paddingRight="8dp"> + android:layout_width="24dp" + android:layout_height="24dp" + android:layout_marginLeft="16dp" + android:layout_marginRight="32dp" + android:scaleType="center" /> + android:textSize="16sp" /> + - + + + + + + - + - + - - - - - + - - + + + + - - - - - - + - + - - - - - + + + + + + + + + + + - + + + + + + \ No newline at end of file diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-ru/ui_text.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-ru/ui_text.xml index a3f9cf19cc..6f6077e8f9 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-ru/ui_text.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-ru/ui_text.xml @@ -332,6 +332,7 @@ В Контактах Добавить в контакты Звонок + Видео звонок Новое сообщение Добавление в Контакты… diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml index 915e0e48c0..75410da959 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml @@ -324,6 +324,7 @@ In Contacts Add to Contacts Voice Call + Video Call New Message Mobile phone From c5491c7aa1027bc043a92be5404c337250c85d9f Mon Sep 17 00:00:00 2001 From: rockjam Date: Sat, 23 Jul 2016 00:37:30 +0300 Subject: [PATCH 300/414] fix(server): add system shutdown hook --- actor-server/src/main/scala/im/actor/server/Main.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/actor-server/src/main/scala/im/actor/server/Main.scala b/actor-server/src/main/scala/im/actor/server/Main.scala index 11185c5fbc..a5a139a0f1 100644 --- a/actor-server/src/main/scala/im/actor/server/Main.scala +++ b/actor-server/src/main/scala/im/actor/server/Main.scala @@ -4,5 +4,7 @@ import scala.concurrent.Await import scala.concurrent.duration.Duration object Main extends App { - Await.result(ActorServer.newBuilder.start().system.whenTerminated, Duration.Inf) + val system = ActorServer.newBuilder.start().system + sys.addShutdownHook(system.terminate()) + Await.result(system.whenTerminated, Duration.Inf) } From d48236d92191f87a5291e9ca757d052e11808ccb Mon Sep 17 00:00:00 2001 From: kor_ka Date: Thu, 25 Aug 2016 14:03:01 +0300 Subject: [PATCH 301/414] fix(android): use sticker layout --- .../sdk/controllers/conversation/messages/DefaultLayouter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/DefaultLayouter.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/DefaultLayouter.java index 7047688d3e..a1861431de 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/DefaultLayouter.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/DefaultLayouter.java @@ -80,7 +80,7 @@ public AbsMessageViewHolder onCreateViewHolder(MessagesAdapter adapter, ViewGrou holderMap.add(new HolderMapEntry(DocumentContent.class, DOCUMENT_CONTENT, R.layout.adapter_dialog_doc)); holderMap.add(new HolderMapEntry(ContactContent.class, CONTACT_CONTENT, R.layout.adapter_dialog_contact)); holderMap.add(new HolderMapEntry(LocationContent.class, LOCATION_CONTENT, R.layout.adapter_dialog_locaton)); - holderMap.add(new HolderMapEntry(StickerContent.class, STICKER_CONTENT, R.layout.adapter_dialog_locaton)); + holderMap.add(new HolderMapEntry(StickerContent.class, STICKER_CONTENT, R.layout.adapter_dialog_sticker)); } private static class HolderMapEntry { From 9f4e87e6f0321bace584f00f877edf0b999e1a82 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Thu, 25 Aug 2016 14:31:04 +0300 Subject: [PATCH 302/414] fix(android): replace DrawableCompat.tint() with color filter --- .../controllers/profile/ProfileFragment.java | 26 ++++++++----------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/profile/ProfileFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/profile/ProfileFragment.java index f78d237b59..f88bc47c93 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/profile/ProfileFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/profile/ProfileFragment.java @@ -7,6 +7,7 @@ import android.content.Intent; import android.content.res.ColorStateList; import android.graphics.Color; +import android.graphics.PorterDuff; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.media.RingtoneManager; @@ -167,9 +168,8 @@ public View onCreateView(final LayoutInflater inflater, ViewGroup container, Bun //add to contacts btn addContactTitle.setText(getString(R.string.profile_contacts_added)); addContactTitle.setTextColor(style.getProfileContactIconColor()); - Drawable drawable = DrawableCompat.wrap(getResources().getDrawable(R.drawable.ic_check_circle_black_24dp)); - drawable.mutate(); - DrawableCompat.setTint(drawable, style.getProfileContactIconColor()); + Drawable drawable = getResources().getDrawable(R.drawable.ic_check_circle_black_24dp); + drawable.mutate().setColorFilter(style.getProfileContactIconColor(), PorterDuff.Mode.SRC_IN); addContactIcon.setImageDrawable(drawable); addContact.setOnClickListener(v -> { execute(ActorSDK.sharedActor().getMessenger().removeContact(user.getId())); @@ -182,9 +182,8 @@ public View onCreateView(final LayoutInflater inflater, ViewGroup container, Bun //add to contacts btn addContactTitle.setText(getString(R.string.profile_contacts_available)); addContactTitle.setTextColor(style.getProfileContactIconColor()); - Drawable drawable = DrawableCompat.wrap(getResources().getDrawable(R.drawable.ic_person_add_white_24dp)); - drawable = drawable.mutate(); - DrawableCompat.setTint(drawable, style.getProfileContactIconColor()); + Drawable drawable = getResources().getDrawable(R.drawable.ic_person_add_white_24dp); + drawable.mutate().setColorFilter(style.getProfileContactIconColor(), PorterDuff.Mode.SRC_IN); addContactIcon.setImageDrawable(drawable); addContact.setOnClickListener(v -> { execute(ActorSDK.sharedActor().getMessenger().addContact(user.getId())); @@ -205,9 +204,8 @@ public View onCreateView(final LayoutInflater inflater, ViewGroup container, Bun ImageView newMessageIcon = (ImageView) newMessageView.findViewById(R.id.newMessageIcon); TextView newMessageTitle = (TextView) newMessageView.findViewById(R.id.newMessageText); { - Drawable drawable = DrawableCompat.wrap(getResources().getDrawable(R.drawable.ic_chat_black_24dp)); - drawable.mutate(); - DrawableCompat.setTint(drawable, style.getListActionColor()); + Drawable drawable = getResources().getDrawable(R.drawable.ic_chat_black_24dp); + drawable.mutate().setColorFilter(style.getProfileContactIconColor(), PorterDuff.Mode.SRC_IN); newMessageIcon.setImageDrawable(drawable); newMessageTitle.setTextColor(style.getListActionColor()); } @@ -224,9 +222,8 @@ public View onCreateView(final LayoutInflater inflater, ViewGroup container, Bun if (ActorSDK.sharedActor().isCallsEnabled() && !user.isBot()) { ImageView voiceViewIcon = (ImageView) voiceCallView.findViewById(R.id.actionIcon); TextView voiceViewTitle = (TextView) voiceCallView.findViewById(R.id.actionText); - Drawable drawable = DrawableCompat.wrap(getResources().getDrawable(R.drawable.ic_phone_white_24dp)); - drawable = drawable.mutate(); - DrawableCompat.setTint(drawable, style.getListActionColor()); + Drawable drawable = getResources().getDrawable(R.drawable.ic_phone_white_24dp); + drawable.mutate().setColorFilter(style.getProfileContactIconColor(), PorterDuff.Mode.SRC_IN); voiceViewIcon.setImageDrawable(drawable); voiceViewTitle.setTextColor(style.getListActionColor()); @@ -246,9 +243,8 @@ public View onCreateView(final LayoutInflater inflater, ViewGroup container, Bun if (ActorSDK.sharedActor().isCallsEnabled() && !user.isBot()) { ImageView voiceViewIcon = (ImageView) videoCallView.findViewById(R.id.videoCallIcon); TextView voiceViewTitle = (TextView) videoCallView.findViewById(R.id.videoCallText); - Drawable drawable = DrawableCompat.wrap(getResources().getDrawable(R.drawable.ic_videocam_white_24dp)); - drawable = drawable.mutate(); - DrawableCompat.setTint(drawable, style.getListActionColor()); + Drawable drawable = getResources().getDrawable(R.drawable.ic_videocam_white_24dp); + drawable.mutate().setColorFilter(style.getProfileContactIconColor(), PorterDuff.Mode.SRC_IN); voiceViewIcon.setImageDrawable(drawable); voiceViewTitle.setTextColor(style.getListActionColor()); From 4088fb38a142eecfa6c8f3eaa3d150bf4a3656ae Mon Sep 17 00:00:00 2001 From: kor_ka Date: Thu, 25 Aug 2016 15:43:45 +0300 Subject: [PATCH 303/414] style(android): reorder profile screen actions --- .../actor/sdk/controllers/BaseFragment.java | 6 +- .../controllers/profile/ProfileFragment.java | 69 ++++++------ .../src/main/res/layout/fragment_profile.xml | 100 +++++++++--------- .../src/main/res/values-ru/ui_text.xml | 2 +- .../src/main/res/values/ui_text.xml | 2 +- 5 files changed, 94 insertions(+), 85 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/BaseFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/BaseFragment.java index c9ba4d86bb..2192ea94b3 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/BaseFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/BaseFragment.java @@ -2,6 +2,7 @@ import android.app.Activity; import android.app.ProgressDialog; +import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; @@ -365,9 +366,8 @@ public View buildRecordBig(String valueText, int resourceId, boolean showIcon, b if (resourceId != 0 && showIcon) { ImageView iconView = (ImageView) recordView.findViewById(R.id.recordIcon); - Drawable drawable = DrawableCompat.wrap(getResources().getDrawable(resourceId)); - drawable.mutate(); - DrawableCompat.setTint(drawable, style.getSettingsIconColor()); + Drawable drawable = getResources().getDrawable(resourceId); + drawable.mutate().setColorFilter(style.getSettingsIconColor(), PorterDuff.Mode.SRC_IN); iconView.setImageDrawable(drawable); } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/profile/ProfileFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/profile/ProfileFragment.java index f88bc47c93..4be52c0b12 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/profile/ProfileFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/profile/ProfileFragment.java @@ -63,6 +63,7 @@ public class ProfileFragment extends BaseFragment { public static int SOUND_PICKER_REQUEST_CODE = 122; public static final String EXTRA_UID = "uid"; + private View recordFieldWithIcon; public static ProfileFragment create(int uid) { Bundle bundle = new Bundle(); @@ -107,7 +108,6 @@ public View onCreateView(final LayoutInflater inflater, ViewGroup container, Bun res.findViewById(R.id.container).setBackgroundColor(style.getMainBackgroundColor()); res.findViewById(R.id.avatarContainer).setBackgroundColor(style.getToolBarColor()); - res.findViewById(R.id.after_actions_divider).setBackgroundColor(style.getBackyardBackgroundColor()); // @@ -157,37 +157,26 @@ public View onCreateView(final LayoutInflater inflater, ViewGroup container, Bun fab.setOnClickListener(v -> startActivity(new Intent(getActivity(), ComposeActivity.class))); // - // Add/Remove Contact + // Remove Contact // - final View addContact = res.findViewById(R.id.addContact); - final ImageView addContactIcon = (ImageView) addContact.findViewById(R.id.addContactIcon); - final TextView addContactTitle = (TextView) addContact.findViewById(R.id.addContactTitle); + final View removeContact = res.findViewById(R.id.addContact); + final TextView addContactTitle = (TextView) removeContact.findViewById(R.id.addContactTitle); + addContactTitle.setText(getString(R.string.profile_contacts_added)); + addContactTitle.setTextColor(style.getTextPrimaryColor()); + removeContact.setOnClickListener(v -> { + execute(ActorSDK.sharedActor().getMessenger().removeContact(user.getId())); + }); + bind(user.isContact(), (isContact, valueModel) -> { if (isContact) { - //add to contacts btn - addContactTitle.setText(getString(R.string.profile_contacts_added)); - addContactTitle.setTextColor(style.getProfileContactIconColor()); - Drawable drawable = getResources().getDrawable(R.drawable.ic_check_circle_black_24dp); - drawable.mutate().setColorFilter(style.getProfileContactIconColor(), PorterDuff.Mode.SRC_IN); - addContactIcon.setImageDrawable(drawable); - addContact.setOnClickListener(v -> { - execute(ActorSDK.sharedActor().getMessenger().removeContact(user.getId())); - }); + removeContact.setVisibility(View.VISIBLE); //fab fab.setImageResource(R.drawable.ic_message_white_24dp); fab.setOnClickListener(view -> startActivity(Intents.openPrivateDialog(user.getId(), true, getActivity()))); } else { - //add to contacts btn - addContactTitle.setText(getString(R.string.profile_contacts_available)); - addContactTitle.setTextColor(style.getProfileContactIconColor()); - Drawable drawable = getResources().getDrawable(R.drawable.ic_person_add_white_24dp); - drawable.mutate().setColorFilter(style.getProfileContactIconColor(), PorterDuff.Mode.SRC_IN); - addContactIcon.setImageDrawable(drawable); - addContact.setOnClickListener(v -> { - execute(ActorSDK.sharedActor().getMessenger().addContact(user.getId())); - }); + removeContact.setVisibility(View.GONE); //fab fab.setImageResource(R.drawable.ic_person_add_white_24dp); @@ -205,9 +194,9 @@ public View onCreateView(final LayoutInflater inflater, ViewGroup container, Bun TextView newMessageTitle = (TextView) newMessageView.findViewById(R.id.newMessageText); { Drawable drawable = getResources().getDrawable(R.drawable.ic_chat_black_24dp); - drawable.mutate().setColorFilter(style.getProfileContactIconColor(), PorterDuff.Mode.SRC_IN); + drawable.mutate().setColorFilter(style.getSettingsIconColor(), PorterDuff.Mode.SRC_IN); newMessageIcon.setImageDrawable(drawable); - newMessageTitle.setTextColor(style.getListActionColor()); + newMessageTitle.setTextColor(style.getTextPrimaryColor()); } newMessageView.setOnClickListener(v -> { startActivity(Intents.openPrivateDialog(user.getId(), true, getActivity())); @@ -223,9 +212,9 @@ public View onCreateView(final LayoutInflater inflater, ViewGroup container, Bun ImageView voiceViewIcon = (ImageView) voiceCallView.findViewById(R.id.actionIcon); TextView voiceViewTitle = (TextView) voiceCallView.findViewById(R.id.actionText); Drawable drawable = getResources().getDrawable(R.drawable.ic_phone_white_24dp); - drawable.mutate().setColorFilter(style.getProfileContactIconColor(), PorterDuff.Mode.SRC_IN); + drawable.mutate().setColorFilter(style.getSettingsIconColor(), PorterDuff.Mode.SRC_IN); voiceViewIcon.setImageDrawable(drawable); - voiceViewTitle.setTextColor(style.getListActionColor()); + voiceViewTitle.setTextColor(style.getTextPrimaryColor()); voiceCallView.setOnClickListener(v -> { execute(ActorSDK.sharedActor().getMessenger().doCall(user.getId())); @@ -244,9 +233,9 @@ public View onCreateView(final LayoutInflater inflater, ViewGroup container, Bun ImageView voiceViewIcon = (ImageView) videoCallView.findViewById(R.id.videoCallIcon); TextView voiceViewTitle = (TextView) videoCallView.findViewById(R.id.videoCallText); Drawable drawable = getResources().getDrawable(R.drawable.ic_videocam_white_24dp); - drawable.mutate().setColorFilter(style.getProfileContactIconColor(), PorterDuff.Mode.SRC_IN); + drawable.mutate().setColorFilter(style.getSettingsIconColor(), PorterDuff.Mode.SRC_IN); voiceViewIcon.setImageDrawable(drawable); - voiceViewTitle.setTextColor(style.getListActionColor()); + voiceViewTitle.setTextColor(style.getTextPrimaryColor()); videoCallView.setOnClickListener(v -> { execute(ActorSDK.sharedActor().getMessenger().doVideoCall(user.getId())); @@ -262,7 +251,8 @@ public View onCreateView(final LayoutInflater inflater, ViewGroup container, Bun final LinearLayout contactsContainer = (LinearLayout) res.findViewById(R.id.contactsContainer); - boolean isFirstContact = true; + String aboutString = user.getAbout().get(); + boolean isFirstContact = aboutString == null || aboutString.isEmpty(); // // About @@ -283,6 +273,9 @@ public void onChanged(final String newUserAbout, Value valueModel) { } else { ((TextView) userAboutRecord.findViewById(R.id.value)).setText(newUserAbout); } + if (recordFieldWithIcon != null) { + recordFieldWithIcon.findViewById(R.id.recordIcon).setVisibility(View.INVISIBLE); + } } } }); @@ -314,14 +307,15 @@ public void onChanged(final String newUserAbout, Value valueModel) { phoneTitle = getString(R.string.settings_mobile_phone); } - View view = buildRecord(phoneTitle, phoneNumber, R.drawable.ic_import_contacts_black_24dp, isFirstContact, emails.size() == 0 && i == phones.size() - 1, inflater, contactsContainer); - + if (isFirstContact) { + recordFieldWithIcon = view; + } view.setOnClickListener(v -> { new AlertDialog.Builder(getActivity()) @@ -381,6 +375,9 @@ public void onChanged(final String newUserAbout, Value valueModel) { isFirstContact, i == emails.size() - 1, inflater, contactsContainer); + if (isFirstContact) { + recordFieldWithIcon = view; + } view.setOnClickListener(v -> { new AlertDialog.Builder(getActivity()) @@ -443,6 +440,10 @@ public void onChanged(final String newUserName, Value valueModel) { .show(); return true; }); + + if (finalIsFirstContact) { + recordFieldWithIcon = userNameRecord; + } } } }); @@ -556,6 +557,10 @@ public void onChanged(final String newUserName, Value valueModel) { return res; } + private void checkInfiIcon() { + + } + @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { if (resultCode == Activity.RESULT_OK && requestCode == SOUND_PICKER_REQUEST_CODE) { diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_profile.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_profile.xml index 80b6d05018..9b11365a9a 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_profile.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_profile.xml @@ -64,31 +64,11 @@ - - - - - - + android:layout_height="wrap_content" + android:orientation="vertical" /> + + + + - - - - - - - - + android:layout_gravity="bottom" + android:layout_marginBottom="8dp" + android:layout_marginLeft="72dp" + android:layout_marginTop="8dp" /> + + + + + + + + Заблокировать пользователя Разблокировать пользователя Заблокировать {user}? - В Контактах + Убрать из контактов Добавить в контакты Звонок Видео звонок diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml index 75410da959..c7f99c741e 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml @@ -321,7 +321,7 @@ Unblock user Block {user}? - In Contacts + Remove from contacts Add to Contacts Voice Call Video Call From 69475cbe76f615e63aff168b5a2d2ddda88c8beb Mon Sep 17 00:00:00 2001 From: kor_ka Date: Thu, 25 Aug 2016 16:17:32 +0300 Subject: [PATCH 304/414] style(android): merge profile info to one block --- .../im/actor/sdk/controllers/profile/ProfileFragment.java | 8 ++++---- .../android-sdk/src/main/res/layout/fragment_profile.xml | 8 ++++++++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/profile/ProfileFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/profile/ProfileFragment.java index 4be52c0b12..3efae67e6f 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/profile/ProfileFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/profile/ProfileFragment.java @@ -268,7 +268,7 @@ public void onChanged(final String newUserAbout, Value valueModel) { userAboutRecord = buildRecordBig(newUserAbout, R.drawable.ic_info_outline_black_24dp, true, - true, + false, inflater, contactsContainer); } else { ((TextView) userAboutRecord.findViewById(R.id.value)).setText(newUserAbout); @@ -311,7 +311,7 @@ public void onChanged(final String newUserAbout, Value valueModel) { phoneNumber, R.drawable.ic_import_contacts_black_24dp, isFirstContact, - emails.size() == 0 && i == phones.size() - 1, + false, inflater, contactsContainer); if (isFirstContact) { recordFieldWithIcon = view; @@ -373,7 +373,7 @@ public void onChanged(final String newUserAbout, Value valueModel) { userEmail.getEmail(), R.drawable.ic_import_contacts_black_24dp, isFirstContact, - i == emails.size() - 1, + false, inflater, contactsContainer); if (isFirstContact) { recordFieldWithIcon = view; @@ -425,7 +425,7 @@ public void onChanged(final String newUserName, Value valueModel) { userNameRecord = buildRecord(getString(R.string.nickname), "@" + newUserName, R.drawable.ic_import_contacts_black_24dp, finalIsFirstContact, - true, + false, inflater, contactsContainer); } else { ((TextView) userNameRecord.findViewById(R.id.value)).setText(newUserName); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_profile.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_profile.xml index 9b11365a9a..fe5028b9b5 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_profile.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_profile.xml @@ -70,6 +70,14 @@ android:layout_height="wrap_content" android:orientation="vertical" /> + + Date: Thu, 25 Aug 2016 18:25:40 +0300 Subject: [PATCH 305/414] style(android): fix icons margins --- .../src/main/res/layout/contact_record.xml | 6 +-- .../main/res/layout/contact_record_big.xml | 4 +- .../src/main/res/layout/fragment_profile.xml | 45 ++++++------------- 3 files changed, 17 insertions(+), 38 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/contact_record.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/contact_record.xml index 41d6b4f6a7..7711a1d00c 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/contact_record.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/contact_record.xml @@ -16,11 +16,9 @@ + android:layout_height="match_parent" /> + android:layout_width="72dp" + android:layout_height="48dp" /> Date: Fri, 26 Aug 2016 14:28:59 +0300 Subject: [PATCH 306/414] chore(android): remove anonymous fragments from example app --- .../src/main/java/im/actor/Application.java | 36 +++------------- .../im/actor/fragments/AttachFragmentEx.java | 27 ++++++++++++ .../im/actor/fragments/RootFragmentEx.java | 15 +++++++ .../messages/DefaultLayouter.java | 41 +++++++++---------- .../messages/MessagesAdapter.java | 27 ++++++------ 5 files changed, 80 insertions(+), 66 deletions(-) create mode 100644 actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/fragments/AttachFragmentEx.java create mode 100644 actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/fragments/RootFragmentEx.java diff --git a/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/Application.java b/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/Application.java index 11d4d05340..34d71d0990 100644 --- a/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/Application.java +++ b/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/Application.java @@ -5,7 +5,6 @@ import android.os.Bundle; import android.support.multidex.MultiDex; import android.support.v4.app.Fragment; -import android.support.v7.app.ActionBar; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -15,22 +14,21 @@ import org.jetbrains.annotations.Nullable; import java.util.ArrayList; -import java.util.List; import im.actor.core.entity.Message; import im.actor.core.entity.Peer; import im.actor.core.entity.content.JsonContent; import im.actor.core.entity.content.PhotoContent; import im.actor.develop.R; +import im.actor.fragments.AttachFragmentEx; +import im.actor.fragments.RootFragmentEx; import im.actor.runtime.json.JSONException; import im.actor.runtime.json.JSONObject; import im.actor.sdk.ActorSDK; import im.actor.sdk.ActorSDKApplication; import im.actor.sdk.ActorStyle; import im.actor.sdk.BaseActorSDKDelegate; -import im.actor.sdk.controllers.conversation.attach.ShareMenuField; import im.actor.sdk.controllers.conversation.attach.AbsAttachFragment; -import im.actor.sdk.controllers.conversation.attach.AttachFragment; import im.actor.sdk.controllers.conversation.messages.BubbleLayouter; import im.actor.sdk.controllers.conversation.messages.DefaultLayouter; import im.actor.sdk.controllers.conversation.messages.JsonXmlBubbleLayouter; @@ -38,7 +36,6 @@ import im.actor.sdk.controllers.conversation.messages.content.PhotoHolder; import im.actor.sdk.controllers.conversation.messages.content.TextHolder; import im.actor.sdk.controllers.conversation.messages.content.preprocessor.PreprocessedData; -import im.actor.sdk.controllers.root.RootFragment; import im.actor.sdk.controllers.settings.ActorSettingsCategories; import im.actor.sdk.controllers.settings.ActorSettingsCategory; import im.actor.sdk.controllers.settings.ActorSettingsField; @@ -105,7 +102,7 @@ private class ActorSDKDelegate extends BaseActorSDKDelegate { @Override public void configureChatViewHolders(ArrayList layouters) { // layouters.add(0, new BubbleTextHolderLayouter()); - layouters.add(0, new DefaultLayouter(DefaultLayouter.TEXT_CONTENT, CensoredTextHolderEx::new)); + layouters.add(0, new DefaultLayouter(DefaultLayouter.TEXT_HOLDER, CensoredTextHolderEx::new)); layouters.add(0, new XmlBubbleLayouter(content -> content instanceof PhotoContent, R.layout.adapter_dialog_photo, (adapter1, root1, peer1) -> new PhotoHolder(adapter1, root1, peer1) { @Override protected void onConfigureViewHolder() { @@ -133,36 +130,13 @@ protected void bindData(Message message, long readDate, long receiveDate, boolea @Nullable @Override public Fragment fragmentForRoot() { - return new RootFragment() { - @Override - public void onConfigureActionBar(ActionBar actionBar) { - super.onConfigureActionBar(actionBar); - actionBar.setDisplayShowHomeEnabled(true); - actionBar.setIcon(R.drawable.ic_app_notify); - } - }; + return new RootFragmentEx(); } @Nullable @Override public AbsAttachFragment fragmentForAttachMenu(Peer peer) { - return new AttachFragment(peer) { - - @Override - protected List onCreateFields() { - List res = super.onCreateFields(); - res.add(new ShareMenuField(R.id.share_test, R.drawable.ic_edit_white_24dp, ActorSDK.sharedActor().style.getAccentColor(), "lol")); - return res; - } - - @Override - protected void onItemClicked(int id) { - super.onItemClicked(id); - if (id == R.id.share_test) { - Toast.makeText(getContext(), "Hey", Toast.LENGTH_LONG).show(); - } - } - }; + return new AttachFragmentEx(); } // @Override diff --git a/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/fragments/AttachFragmentEx.java b/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/fragments/AttachFragmentEx.java new file mode 100644 index 0000000000..e385a291e3 --- /dev/null +++ b/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/fragments/AttachFragmentEx.java @@ -0,0 +1,27 @@ +package im.actor.fragments; + +import android.widget.Toast; + +import java.util.List; + +import im.actor.develop.R; +import im.actor.sdk.ActorSDK; +import im.actor.sdk.controllers.conversation.attach.AttachFragment; +import im.actor.sdk.controllers.conversation.attach.ShareMenuField; + +public class AttachFragmentEx extends AttachFragment { + @Override + protected List onCreateFields() { + List res = super.onCreateFields(); + res.add(new ShareMenuField(R.id.share_test, R.drawable.ic_edit_white_24dp, ActorSDK.sharedActor().style.getAccentColor(), "lol")); + return res; + } + + @Override + protected void onItemClicked(int id) { + super.onItemClicked(id); + if (id == R.id.share_test) { + Toast.makeText(getContext(), "Hey", Toast.LENGTH_LONG).show(); + } + } +} diff --git a/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/fragments/RootFragmentEx.java b/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/fragments/RootFragmentEx.java new file mode 100644 index 0000000000..4af9246bfe --- /dev/null +++ b/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/fragments/RootFragmentEx.java @@ -0,0 +1,15 @@ +package im.actor.fragments; + +import android.support.v7.app.ActionBar; + +import im.actor.develop.R; +import im.actor.sdk.controllers.root.RootFragment; + +public class RootFragmentEx extends RootFragment { + @Override + public void onConfigureActionBar(ActionBar actionBar) { + super.onConfigureActionBar(actionBar); + actionBar.setDisplayShowHomeEnabled(true); + actionBar.setIcon(R.drawable.ic_app_notify); + } +} diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/DefaultLayouter.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/DefaultLayouter.java index a1861431de..b07d37e33e 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/DefaultLayouter.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/DefaultLayouter.java @@ -1,6 +1,5 @@ package im.actor.sdk.controllers.conversation.messages; -import android.support.annotation.LayoutRes; import android.view.View; import android.view.ViewGroup; @@ -27,21 +26,21 @@ public class DefaultLayouter extends LambdaBubbleLayouter { - public static final int TEXT_CONTENT = 0; - public static final int SERVICE_CONTENT = 1; - public static final int PHOTO_CONTENT = 2; - public static final int VOICE_CONTENT = 4; - public static final int DOCUMENT_CONTENT = 3; - public static final int CONTACT_CONTENT = 5; - public static final int LOCATION_CONTENT = 6; - public static final int STICKER_CONTENT = 7; + public static final int TEXT_HOLDER = 0; + public static final int SERVICE_HOLDER = 1; + public static final int PHOTO_HOLDER = 2; + public static final int VOICE_HOLDER = 4; + public static final int DOCUMENT_HOLDER = 3; + public static final int CONTACT_HOLDER = 5; + public static final int LOCATION_HOLDER = 6; + public static final int STICKER_HOLDER = 7; int id; int layoutId; - public DefaultLayouter(int id, @NotNull ViewHolderCreator creator) { + public DefaultLayouter(int holderId, @NotNull ViewHolderCreator creator) { super(content -> false, creator); - this.id = id; + this.id = holderId; } @@ -71,16 +70,16 @@ public AbsMessageViewHolder onCreateViewHolder(MessagesAdapter adapter, ViewGrou static { holderMap = new ArrayList<>(); - holderMap.add(new HolderMapEntry(TextContent.class, TEXT_CONTENT, R.layout.adapter_dialog_text)); - holderMap.add(new HolderMapEntry(ServiceContent.class, SERVICE_CONTENT, R.layout.adapter_dialog_service)); - holderMap.add(new HolderMapEntry(PhotoContent.class, PHOTO_CONTENT, R.layout.adapter_dialog_photo)); - holderMap.add(new HolderMapEntry(VideoContent.class, PHOTO_CONTENT, R.layout.adapter_dialog_photo)); - holderMap.add(new HolderMapEntry(AnimationContent.class, PHOTO_CONTENT, R.layout.adapter_dialog_photo)); - holderMap.add(new HolderMapEntry(VoiceContent.class, VOICE_CONTENT, R.layout.adapter_dialog_audio)); - holderMap.add(new HolderMapEntry(DocumentContent.class, DOCUMENT_CONTENT, R.layout.adapter_dialog_doc)); - holderMap.add(new HolderMapEntry(ContactContent.class, CONTACT_CONTENT, R.layout.adapter_dialog_contact)); - holderMap.add(new HolderMapEntry(LocationContent.class, LOCATION_CONTENT, R.layout.adapter_dialog_locaton)); - holderMap.add(new HolderMapEntry(StickerContent.class, STICKER_CONTENT, R.layout.adapter_dialog_sticker)); + holderMap.add(new HolderMapEntry(TextContent.class, TEXT_HOLDER, R.layout.adapter_dialog_text)); + holderMap.add(new HolderMapEntry(ServiceContent.class, SERVICE_HOLDER, R.layout.adapter_dialog_service)); + holderMap.add(new HolderMapEntry(PhotoContent.class, PHOTO_HOLDER, R.layout.adapter_dialog_photo)); + holderMap.add(new HolderMapEntry(VideoContent.class, PHOTO_HOLDER, R.layout.adapter_dialog_photo)); + holderMap.add(new HolderMapEntry(AnimationContent.class, PHOTO_HOLDER, R.layout.adapter_dialog_photo)); + holderMap.add(new HolderMapEntry(VoiceContent.class, VOICE_HOLDER, R.layout.adapter_dialog_audio)); + holderMap.add(new HolderMapEntry(DocumentContent.class, DOCUMENT_HOLDER, R.layout.adapter_dialog_doc)); + holderMap.add(new HolderMapEntry(ContactContent.class, CONTACT_HOLDER, R.layout.adapter_dialog_contact)); + holderMap.add(new HolderMapEntry(LocationContent.class, LOCATION_HOLDER, R.layout.adapter_dialog_locaton)); + holderMap.add(new HolderMapEntry(StickerContent.class, STICKER_HOLDER, R.layout.adapter_dialog_sticker)); } private static class HolderMapEntry { diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/MessagesAdapter.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/MessagesAdapter.java index 4b3233651a..018380a34a 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/MessagesAdapter.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/MessagesAdapter.java @@ -1,7 +1,6 @@ package im.actor.sdk.controllers.conversation.messages; import android.content.Context; -import android.view.View; import android.view.ViewGroup; import java.util.HashMap; @@ -35,7 +34,7 @@ public class MessagesAdapter extends BindedListAdapter displayList, matcher = new ViewHolderMatcher(); - matcher.add(new DefaultLayouter(DefaultLayouter.TEXT_CONTENT, TextHolder::new)); - matcher.add(new DefaultLayouter(DefaultLayouter.SERVICE_CONTENT, ServiceHolder::new)); - matcher.add(new DefaultLayouter(DefaultLayouter.PHOTO_CONTENT, PhotoHolder::new)); - matcher.add(new DefaultLayouter(DefaultLayouter.VOICE_CONTENT, AudioHolder::new)); - matcher.add(new DefaultLayouter(DefaultLayouter.DOCUMENT_CONTENT, DocHolder::new)); - matcher.add(new DefaultLayouter(DefaultLayouter.CONTACT_CONTENT, ContactHolder::new)); - matcher.add(new DefaultLayouter(DefaultLayouter.LOCATION_CONTENT, LocationHolder::new)); - matcher.add(new DefaultLayouter(DefaultLayouter.STICKER_CONTENT, StickerHolder::new)); + matcher.add(new DefaultLayouter(DefaultLayouter.TEXT_HOLDER, TextHolder::new)); + matcher.add(new DefaultLayouter(DefaultLayouter.SERVICE_HOLDER, ServiceHolder::new)); + matcher.add(new DefaultLayouter(DefaultLayouter.PHOTO_HOLDER, PhotoHolder::new)); + matcher.add(new DefaultLayouter(DefaultLayouter.VOICE_HOLDER, AudioHolder::new)); + matcher.add(new DefaultLayouter(DefaultLayouter.DOCUMENT_HOLDER, DocHolder::new)); + matcher.add(new DefaultLayouter(DefaultLayouter.CONTACT_HOLDER, ContactHolder::new)); + matcher.add(new DefaultLayouter(DefaultLayouter.LOCATION_HOLDER, LocationHolder::new)); + matcher.add(new DefaultLayouter(DefaultLayouter.STICKER_HOLDER, StickerHolder::new)); ActorSDK.sharedActor().getDelegate().configureChatViewHolders(matcher.getLayouters()); @@ -171,11 +170,11 @@ public AbsMessageViewHolder onCreateViewHolder(final ViewGroup viewGroup, int vi public void onBindViewHolder(AbsMessageViewHolder dialogHolder, int index, Message item) { Message prev = null; Message next = null; - if (index > DefaultLayouter.SERVICE_CONTENT) { - next = getItem(index - DefaultLayouter.SERVICE_CONTENT); + if (index > 1) { + next = getItem(index - 1); } - if (index < getItemCount() - DefaultLayouter.SERVICE_CONTENT) { - prev = getItem(index + DefaultLayouter.SERVICE_CONTENT); + if (index < getItemCount() - 1) { + prev = getItem(index + 1); } PreprocessedList list = ((PreprocessedList) getPreprocessedList()); dialogHolder.bindData(item, prev, next, readDate, receiveDate, list.getPreprocessedData()[index]); From ba1c96d0069532ca1c1d9530b01fc453ddbfeb5c Mon Sep 17 00:00:00 2001 From: kor_ka Date: Fri, 26 Aug 2016 14:44:36 +0300 Subject: [PATCH 307/414] chore(android): fix leave/delete group messages --- .../controllers/dialogs/DialogsDefaultFragment.java | 4 ++-- .../sdk/controllers/group/GroupAdminFragment.java | 12 +++++++----- .../sdk/controllers/group/GroupInfoFragment.java | 2 +- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/DialogsDefaultFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/DialogsDefaultFragment.java index 768fb33d7f..05efb48533 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/DialogsDefaultFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/DialogsDefaultFragment.java @@ -80,7 +80,7 @@ public void onError(Exception e) { getString(dialogs_menu_rename), getString(groupVM.getIsCanLeave().get() ? dialogs_menu_leave : groupVM.getIsCanDelete().get() ? dialogs_menu_delete : - dialogs_menu_delete), + dialogs_menu_leave), }; new AlertDialog.Builder(getActivity()) .setItems(items, (d, which) -> { @@ -92,7 +92,7 @@ public void onError(Exception e) { int alert_delete_title = groupVM.getGroupType() == GroupType.CHANNEL ? R.string.alert_delete_channel_title : R.string.alert_delete_group_title; int alert_leave_message = groupVM.getGroupType() == GroupType.CHANNEL ? R.string.alert_leave_channel_message : R.string.alert_leave_group_message; new AlertDialog.Builder(getActivity()) - .setMessage(getString(groupVM.getIsCanLeave().get() ? alert_delete_title : + .setMessage(getString(groupVM.getIsCanLeave().get() ? alert_leave_message : groupVM.getIsCanDelete().get() ? alert_delete_title : alert_leave_message, dialog.getDialogTitle())) .setNegativeButton(R.string.dialog_cancel, null) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupAdminFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupAdminFragment.java index ef2a32fc54..527d8e51a5 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupAdminFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupAdminFragment.java @@ -137,6 +137,7 @@ public void onChanged(String val, Value valueModel) { delete.setTextColor(style.getTextDangerColor()); TextView deleteHint = (TextView) res.findViewById(R.id.deleteHint); deleteHint.setTextColor(style.getTextSecondaryColor()); + if (groupVM.getGroupType() == GroupType.CHANNEL) { delete.setText(R.string.channel_delete); deleteHint.setText(R.string.channel_delete_hint); @@ -146,14 +147,15 @@ public void onChanged(String val, Value valueModel) { } bind(groupVM.getIsCanLeave(), groupVM.getIsCanDelete(), (canLeave, canDelete) -> { - if (canDelete || canDelete) { + if (canLeave || canDelete) { deleteContainer.setVisibility(View.VISIBLE); delete.setOnClickListener(v -> { - + int alert_delete_title = groupVM.getGroupType() == GroupType.CHANNEL ? R.string.alert_delete_channel_title : R.string.alert_delete_group_title; + int alert_leave_message = groupVM.getGroupType() == GroupType.CHANNEL ? R.string.alert_leave_channel_message : R.string.alert_leave_group_message; new AlertDialog.Builder(getActivity()) - .setMessage(getString(groupVM.getIsCanLeave().get() ? R.string.alert_delete_group_title : - groupVM.getIsCanDelete().get() ? R.string.alert_delete_group_title : - R.string.alert_leave_group_message, groupVM.getName().get())) + .setMessage(getString(groupVM.getIsCanLeave().get() ? alert_leave_message : + groupVM.getIsCanDelete().get() ? alert_delete_title : + alert_leave_message, groupVM.getName().get())) .setNegativeButton(R.string.dialog_cancel, null) .setPositiveButton(R.string.alert_delete_group_yes, (d1, which1) -> { if (groupVM.getIsCanLeave().get()) { diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java index 2d25c54071..8cbaf20a9e 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupInfoFragment.java @@ -270,7 +270,7 @@ public void onClick(View view) { leaveAction.setVisibility(View.VISIBLE); leaveAction.setOnClickListener(view1 -> { new AlertDialog.Builder(getActivity()) - .setMessage(getString(R.string.alert_leave_group_message).replace("%1$s", + .setMessage(getString(groupVM.getGroupType() == GroupType.CHANNEL ? R.string.alert_leave_channel_message : R.string.alert_leave_group_message).replace("%1$s", groupVM.getName().get())) .setPositiveButton(R.string.alert_leave_group_yes, (dialog2, which) -> { execute(messenger().leaveAndDeleteGroup(chatId).then(aVoid -> ActorSDK.returnToRoot(getActivity()))); From d50649cdbddf3408ce4938b2c8e9861c0ed53249 Mon Sep 17 00:00:00 2001 From: Nikolay Tatarinov Date: Sat, 27 Aug 2016 16:35:59 +0300 Subject: [PATCH 308/414] Update open source group invite link --- README.MD | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.MD b/README.MD index fca67c1fac..a64b897c84 100644 --- a/README.MD +++ b/README.MD @@ -48,7 +48,7 @@ Our [Actor Bootstrap](https://github.com/actorapp/actor-bootstrap) repository co # Community Support -Keep in touch with the Actor community in our [group chat](https://quit.email/join/0d43e6a90d108ad9608514b5c17b76d5b2721d5e2ea51058d6ca43a66befe7f4). +Keep in touch with the Actor community in our [group chat](https://actor.im/join/actor_oss). # Contacts From fdbf9ec43f5f90836dc97427c28e3e6412919e35 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Mon, 29 Aug 2016 17:44:55 +0300 Subject: [PATCH 309/414] chore(android): fix example app attach menu custom fragment --- .../android-app/src/main/java/im/actor/Application.java | 2 +- .../main/java/im/actor/fragments/AttachFragmentEx.java | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/Application.java b/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/Application.java index 34d71d0990..edae9ae988 100644 --- a/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/Application.java +++ b/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/Application.java @@ -136,7 +136,7 @@ public Fragment fragmentForRoot() { @Nullable @Override public AbsAttachFragment fragmentForAttachMenu(Peer peer) { - return new AttachFragmentEx(); + return new AttachFragmentEx(peer); } // @Override diff --git a/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/fragments/AttachFragmentEx.java b/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/fragments/AttachFragmentEx.java index e385a291e3..2a527773f8 100644 --- a/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/fragments/AttachFragmentEx.java +++ b/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/fragments/AttachFragmentEx.java @@ -4,12 +4,21 @@ import java.util.List; +import im.actor.core.entity.Peer; import im.actor.develop.R; import im.actor.sdk.ActorSDK; import im.actor.sdk.controllers.conversation.attach.AttachFragment; import im.actor.sdk.controllers.conversation.attach.ShareMenuField; public class AttachFragmentEx extends AttachFragment { + + public AttachFragmentEx(Peer peer) { + super(peer); + } + + public AttachFragmentEx() { + } + @Override protected List onCreateFields() { List res = super.onCreateFields(); From 19428c515b23c7a828c77be3c46a1adb0e5091be Mon Sep 17 00:00:00 2001 From: kor_ka Date: Mon, 29 Aug 2016 19:29:05 +0300 Subject: [PATCH 310/414] fix(android): pick last message for notification configure --- .../src/main/java/im/actor/sdk/BaseActorSDKDelegate.java | 8 ++------ .../main/java/im/actor/sdk/core/AndroidNotifications.java | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/BaseActorSDKDelegate.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/BaseActorSDKDelegate.java index edf1ff5a7e..f9725ba295 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/BaseActorSDKDelegate.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/BaseActorSDKDelegate.java @@ -122,12 +122,8 @@ public ActorIntent getChatIntent(Peer peer, boolean compose) { public Uri getNotificationSoundForPeer(Peer peer) { String globalSound = messenger().getPreferences().getString("userNotificationSound_" + peer.getPeerId()); - if (globalSound != null) { - if (globalSound.equals("none")) { - return null; - } else { - return Uri.parse(globalSound); - } + if (globalSound != null && !globalSound.equals("none")) { + return Uri.parse(globalSound); } return getNotificationSound(); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/core/AndroidNotifications.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/core/AndroidNotifications.java index 2c3200fb64..0a925c48cc 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/core/AndroidNotifications.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/core/AndroidNotifications.java @@ -91,7 +91,7 @@ public void onNotification(Messenger messenger, List topNotificati // .setBackground(((BitmapDrawable) AppContext.getContext().getResources().getDrawable(R.drawable.wear_bg)).getBitmap()) // .setHintHideIcon(true)); - final Notification topNotification = topNotifications.get(0); + final Notification topNotification = topNotifications.get(topNotifications.size() - 1); // if (!silentUpdate) { // builder.setTicker(getNotificationTextFull(topNotification, messenger)); From 10879ce9dd11d61b64d7dbaae441a663eb768b71 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Tue, 30 Aug 2016 12:33:38 +0300 Subject: [PATCH 311/414] fix(android): prevent double scroll to unread --- .../conversation/messages/MessagesFragment.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/MessagesFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/MessagesFragment.java index f05b51acab..6e8f1995bd 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/MessagesFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/MessagesFragment.java @@ -18,6 +18,7 @@ import im.actor.core.entity.Message; import im.actor.core.entity.Peer; import im.actor.core.viewmodel.ConversationVM; +import im.actor.runtime.Log; import im.actor.sdk.ActorSDK; import im.actor.sdk.R; import im.actor.sdk.controllers.conversation.messages.content.AudioHolder; @@ -175,13 +176,17 @@ protected BindedDisplayList onCreateDisplayList() { // private void recalculateUnreadMessageIfNeeded() { + Log.d("READ_DEBUG", "trying to scroll to unread"); + // Scroll to unread only in primary mode if (!isPrimaryMode) { return; } BindedDisplayList list = getDisplayList(); - firstUnread = conversationVM.getLastMessageDate(); + if (firstUnread == -1) { + firstUnread = conversationVM.getLastMessageDate(); + } // Do not scroll to unread twice if (isUnreadLoaded) { From 93120d77cb9878ddc97150bceeb081245f7edb35 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Tue, 30 Aug 2016 13:06:21 +0300 Subject: [PATCH 312/414] fix(android): rebind chat for notification intent --- .../conversation/ChatActivity.java | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatActivity.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatActivity.java index e73d12e048..5edd333c91 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatActivity.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatActivity.java @@ -95,16 +95,26 @@ public void onCreate(Bundle saveInstance) { // if (saveInstance == null) { - Peer peer = Peer.fromUniqueId(getIntent().getExtras().getLong(EXTRA_CHAT_PEER)); - chatFragment = ActorSDK.sharedActor().getDelegate().fragmentForChat(peer); - if (chatFragment == null) { - chatFragment = ChatFragment.create(peer); - } - getSupportFragmentManager().beginTransaction() - .add(R.id.chatFragment, chatFragment) - .commitNow(); - quote = getIntent().getStringExtra("forward_text_raw"); + handleIntent(getIntent()); + } + } + + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + handleIntent(intent); + } + + protected void handleIntent(Intent intent) { + Peer peer = Peer.fromUniqueId(intent.getExtras().getLong(EXTRA_CHAT_PEER)); + chatFragment = ActorSDK.sharedActor().getDelegate().fragmentForChat(peer); + if (chatFragment == null) { + chatFragment = ChatFragment.create(peer); } + getSupportFragmentManager().beginTransaction() + .add(R.id.chatFragment, chatFragment) + .commitNow(); + quote = intent.getStringExtra("forward_text_raw"); } @Override From fcbd49ee5f66ed5a3090f89084fdbb4f3d6d84b3 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Tue, 30 Aug 2016 18:21:19 +0300 Subject: [PATCH 313/414] fix(android): use app label for share activity --- .../sdk-core-android/android-sdk/src/main/AndroidManifest.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/AndroidManifest.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/AndroidManifest.xml index fedd697df2..6c13c8964f 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/AndroidManifest.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/AndroidManifest.xml @@ -95,7 +95,6 @@ From 4044ed723934edf3eadf2d066ed07d05a161f5d6 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Wed, 31 Aug 2016 13:25:04 +0300 Subject: [PATCH 314/414] fix(core): add new arguments to onPushReceived ObjectiveCName --- .../core/core-shared/src/main/java/im/actor/core/Messenger.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java index 429574d282..59137e85fd 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java @@ -693,7 +693,7 @@ public void onNetworkChanged(@NotNull NetworkState state) { * @param seq sequence number of update * @param authId auth id */ - @ObjectiveCName("onPushReceivedWithSeq:") + @ObjectiveCName("onPushReceivedWithSeq:withAuthId:") public void onPushReceived(int seq, long authId) { if (modules.getUpdatesModule() != null) { modules.getUpdatesModule().onPushReceived(seq, authId); From 0e47d5fc636658bb98216db89172956ae56e0fe5 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Wed, 31 Aug 2016 13:55:40 +0300 Subject: [PATCH 315/414] fix(android): handle call permission for groups --- .../controllers/conversation/toolbar/ChatToolbarFragment.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/toolbar/ChatToolbarFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/toolbar/ChatToolbarFragment.java index 83398ef06b..0d9338537e 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/toolbar/ChatToolbarFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/toolbar/ChatToolbarFragment.java @@ -261,7 +261,7 @@ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { } else if (peer.getPeerType() == PeerType.GROUP) { GroupVM groupVM = groups().get(peer.getPeerId()); - if (groupVM.getGroupType() == GroupType.GROUP) { + if (groupVM.getGroupType() == GroupType.GROUP && groupVM.isMember().get() && groupVM.getIsCanCall().get()) { callsEnabled = groupVM.getMembersCount().get() <= MAX_USERS_FOR_CALLS; videoCallsEnabled = false; } else { From fc3139f211c42b68ccd7ad8f4e3d11833bedf3cb Mon Sep 17 00:00:00 2001 From: kor_ka Date: Thu, 1 Sep 2016 14:41:32 +0300 Subject: [PATCH 316/414] fix(core): serialize inMaxMessageDate --- .../src/main/java/im/actor/Application.java | 10 +++++++++- .../android-sdk/src/main/AndroidManifest.xml | 1 - .../conversation/messages/MessagesFragment.java | 2 +- .../java/im/actor/core/entity/ConversationState.java | 2 ++ 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/Application.java b/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/Application.java index edae9ae988..f20f359837 100644 --- a/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/Application.java +++ b/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/Application.java @@ -5,6 +5,7 @@ import android.os.Bundle; import android.support.multidex.MultiDex; import android.support.v4.app.Fragment; +import android.text.Spannable; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -102,7 +103,14 @@ private class ActorSDKDelegate extends BaseActorSDKDelegate { @Override public void configureChatViewHolders(ArrayList layouters) { // layouters.add(0, new BubbleTextHolderLayouter()); - layouters.add(0, new DefaultLayouter(DefaultLayouter.TEXT_HOLDER, CensoredTextHolderEx::new)); + layouters.add(0, new DefaultLayouter(DefaultLayouter.TEXT_HOLDER, (adapter2, root2, peer2) -> new TextHolder(adapter2, root2, peer2){ + @Override + public void bindRawText(CharSequence rawText, long readDate, long receiveDate, Spannable reactions, Message message, boolean isItalic) { + super.bindRawText(rawText, readDate, receiveDate, reactions, message, isItalic); + text.append("\n\n" + message.getSortDate()); + } + })); +// layouters.add(0, new DefaultLayouter(DefaultLayouter.TEXT_HOLDER, CensoredTextHolderEx::new)); layouters.add(0, new XmlBubbleLayouter(content -> content instanceof PhotoContent, R.layout.adapter_dialog_photo, (adapter1, root1, peer1) -> new PhotoHolder(adapter1, root1, peer1) { @Override protected void onConfigureViewHolder() { diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/AndroidManifest.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/AndroidManifest.xml index 6c13c8964f..16b41d5c31 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/AndroidManifest.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/AndroidManifest.xml @@ -64,7 +64,6 @@ diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/MessagesFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/MessagesFragment.java index 6e8f1995bd..bfe453f664 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/MessagesFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/MessagesFragment.java @@ -202,7 +202,7 @@ private void recalculateUnreadMessageIfNeeded() { isUnreadLoaded = true; // If don't have unread message date: nothing to do - if (firstUnread == 0) { + if (firstUnread <= 0) { return; } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/ConversationState.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/ConversationState.java index 05fd6b03c5..83e458408a 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/ConversationState.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/ConversationState.java @@ -135,6 +135,7 @@ public void parse(BserValues values) throws IOException { outReadDate = values.getLong(5, 0); unreadCount = values.getInt(6); outSendDate = values.getLong(7, 0); + inMaxMessageDate = values.getLong(9, 0); } @Override @@ -147,6 +148,7 @@ public void serialize(BserWriter writer) throws IOException { writer.writeLong(5, outReadDate); writer.writeInt(6, unreadCount); writer.writeLong(7, outSendDate); + writer.writeLong(9, inMaxMessageDate); } @Override From 7b72e95db731b7bfe88d232d21eaa72c1ed9dac7 Mon Sep 17 00:00:00 2001 From: rockjam Date: Thu, 1 Sep 2016 15:14:49 +0300 Subject: [PATCH 317/414] fix(server): add region to s3 file adapter --- .../src/main/resources/reference.conf | 4 +-- .../server/file/s3/S3StorageAdapter.scala | 18 ++++++---- .../file/s3/S3StorageAdapterConfig.scala | 36 ++++++++++--------- .../src/universal/conf/server.conf.example | 18 +++++----- 4 files changed, 42 insertions(+), 34 deletions(-) diff --git a/actor-server/actor-fs-adapters/src/main/resources/reference.conf b/actor-server/actor-fs-adapters/src/main/resources/reference.conf index 468bce1ea7..cadaf8bfff 100644 --- a/actor-server/actor-fs-adapters/src/main/resources/reference.conf +++ b/actor-server/actor-fs-adapters/src/main/resources/reference.conf @@ -7,10 +7,8 @@ services { aws { s3 { - # provide your custom S3 endpoint if needed - endpoint: "" # enable S3 URLs with path-style access if needed path-style-access: false } } -} \ No newline at end of file +} diff --git a/actor-server/actor-fs-adapters/src/main/scala/im/actor/server/file/s3/S3StorageAdapter.scala b/actor-server/actor-fs-adapters/src/main/scala/im/actor/server/file/s3/S3StorageAdapter.scala index 653228bd0d..b303532ef6 100644 --- a/actor-server/actor-fs-adapters/src/main/scala/im/actor/server/file/s3/S3StorageAdapter.scala +++ b/actor-server/actor-fs-adapters/src/main/scala/im/actor/server/file/s3/S3StorageAdapter.scala @@ -9,6 +9,7 @@ import akka.stream.{ ActorMaterializer, Materializer } import akka.util.ByteString import com.amazonaws.HttpMethod import com.amazonaws.auth.BasicAWSCredentials +import com.amazonaws.regions.{ Region, Regions } import com.amazonaws.services.s3.model.{ GeneratePresignedUrlRequest, ObjectMetadata } import com.amazonaws.services.s3.S3ClientOptions import com.amazonaws.services.s3.transfer.TransferManager @@ -32,18 +33,23 @@ final class S3StorageAdapter(_system: ActorSystem) extends FileStorageAdapter { private implicit val ec: ExecutionContext = system.dispatcher private implicit val mat: Materializer = ActorMaterializer() - private val config = S3StorageAdapterConfig.load(system.settings.config.getConfig("services.aws.s3")).get + private val config = S3StorageAdapterConfig.load private val bucketName = config.bucketName private val awsCredentials = new BasicAWSCredentials(config.key, config.secret) private val db = DbExtension(system).db - val s3Client = new AmazonS3ScalaClient(awsCredentials) - if (!config.endpoint.isEmpty) { - s3Client.client.setEndpoint(config.endpoint) - + val s3Client = { + val blueprint = new AmazonS3ScalaClient(awsCredentials) + config.endpoint foreach { endpoint ⇒ + blueprint.client.setEndpoint(endpoint) + } + config.region foreach { region ⇒ + blueprint.client.setRegion(Region.getRegion(Regions.fromName(region))) + } if (config.pathStyleAccess) { - s3Client.client.setS3ClientOptions(new S3ClientOptions().withPathStyleAccess(true)); + blueprint.client.setS3ClientOptions(new S3ClientOptions().withPathStyleAccess(true)) } + blueprint } val transferManager = new TransferManager(s3Client.client) diff --git a/actor-server/actor-fs-adapters/src/main/scala/im/actor/server/file/s3/S3StorageAdapterConfig.scala b/actor-server/actor-fs-adapters/src/main/scala/im/actor/server/file/s3/S3StorageAdapterConfig.scala index 6d5f06b2c7..751fee7602 100644 --- a/actor-server/actor-fs-adapters/src/main/scala/im/actor/server/file/s3/S3StorageAdapterConfig.scala +++ b/actor-server/actor-fs-adapters/src/main/scala/im/actor/server/file/s3/S3StorageAdapterConfig.scala @@ -3,23 +3,25 @@ package im.actor.server.file.s3 import com.github.kxbmap.configs.syntax._ import com.typesafe.config.{ ConfigFactory, Config } -import scala.util.Try +private[s3] case class S3StorageAdapterConfig( + bucketName: String, + region: Option[String], + key: String, + secret: String, + endpoint: Option[String], + pathStyleAccess: Boolean +) -case class S3StorageAdapterConfig(bucketName: String, key: String, secret: String, endpoint: String, pathStyleAccess: Boolean) - -object S3StorageAdapterConfig { - def load(config: Config): Try[S3StorageAdapterConfig] = { - for { - bucketName ← config.get[Try[String]]("default-bucket") - key ← config.get[Try[String]]("access-key") - secret ← config.get[Try[String]]("secret-key") - endpoint ← config.get[Try[String]]("endpoint") - pathStyleAccess ← config.get[Try[Boolean]]("path-style-access") - } yield S3StorageAdapterConfig(bucketName, key, secret, endpoint, pathStyleAccess) - } - - def load: Try[S3StorageAdapterConfig] = { - val config = ConfigFactory.load().getConfig("services.aws.s3") - load(config) +private[s3] object S3StorageAdapterConfig { + def load(config: Config): S3StorageAdapterConfig = { + S3StorageAdapterConfig( + bucketName = config.get[String]("default-bucket"), + region = config.getOpt[String]("region"), + key = config.get[String]("access-key"), + secret = config.get[String]("secret-key"), + endpoint = config.getOpt[String]("endpoint"), + pathStyleAccess = config.get[Boolean]("path-style-access") + ) } + def load: S3StorageAdapterConfig = load(ConfigFactory.load().getConfig("services.aws.s3")) } diff --git a/actor-server/src/universal/conf/server.conf.example b/actor-server/src/universal/conf/server.conf.example index 89f82d437d..a4b7e43ea8 100644 --- a/actor-server/src/universal/conf/server.conf.example +++ b/actor-server/src/universal/conf/server.conf.example @@ -97,22 +97,24 @@ services { # AWS configuration # It is strictly recommended to configure s3 storage for # enabling file sharing in apps - aws { + # aws { # S3 Storage, used for file sharing # For more information see https://github.com/actorapp/actor-bootstrap/blob/master/docs/server/configure-s3.md - s3 { + # s3 { + # Bucket name + # default-bucket: # S3 Api Key - access-key: + # access-key: # S3 Api Secret - secret-key: - # File bucket - default-bucket: + # secret-key: # S3 Endpoint # endpoint: + # S3 bucket region + # region: # Enable S3 URLs with path-style access # path-style-access: true / false - } - } + # } + # } # Service used for sending activation codes activation { From 3916c2743cb264195da93ee52d40b7643796dbb0 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Thu, 1 Sep 2016 16:09:45 +0300 Subject: [PATCH 318/414] fix(core + android): refresh chat display list on chat open if top message is too old --- .../controllers/conversation/messages/MessagesFragment.java | 6 ++++++ .../im/actor/runtime/generic/mvvm/BindedDisplayList.java | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/MessagesFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/MessagesFragment.java index bfe453f664..ddf91688ba 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/MessagesFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/MessagesFragment.java @@ -198,6 +198,12 @@ private void recalculateUnreadMessageIfNeeded() { return; } + // refresh list if top message is too old + if (getDisplayList().getItem(0).getSortDate() < firstUnread) { + getDisplayList().initCenter(firstUnread, true); + return; + } + // If List is not empty: mark as loaded isUnreadLoaded = true; diff --git a/actor-sdk/sdk-core/runtime/runtime-generic/src/main/java/im/actor/runtime/generic/mvvm/BindedDisplayList.java b/actor-sdk/sdk-core/runtime/runtime-generic/src/main/java/im/actor/runtime/generic/mvvm/BindedDisplayList.java index ef71ef2c98..c4b14750fd 100644 --- a/actor-sdk/sdk-core/runtime/runtime-generic/src/main/java/im/actor/runtime/generic/mvvm/BindedDisplayList.java +++ b/actor-sdk/sdk-core/runtime/runtime-generic/src/main/java/im/actor/runtime/generic/mvvm/BindedDisplayList.java @@ -233,7 +233,7 @@ public void onLoaded(List items, long topSortKey, long bottomSortKey) { public void initCenter(long centerSortKey, boolean refresh) { // im.actor.runtime.Runtime.checkMainThread(); - if (mode != null && mode == ListMode.CENTER) { + if (!refresh && mode != null && mode == ListMode.CENTER) { return; } mode = ListMode.CENTER; From 0735788da96db89d5b111fed2ed6fea6457c6711 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Fri, 2 Sep 2016 19:58:16 +0300 Subject: [PATCH 319/414] feat(android): autocomplete fragment as bottom sheet --- .../conversation/ChatFragment.java | 1 + .../mentions/AutocompleteFragment.java | 49 +++++--- .../mentions/MentionsAdapter.java | 1 + .../view/adapters/BottomSheetListView.java | 115 ++++++++++++++++++ .../res/drawable/selectable_background.xml | 11 ++ .../src/main/res/values/colors.xml | 4 + 6 files changed, 161 insertions(+), 20 deletions(-) create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/adapters/BottomSheetListView.java create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable/selectable_background.xml diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatFragment.java index 0366519b63..b26975ec76 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatFragment.java @@ -105,6 +105,7 @@ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, AutocompleteFragment autocompleteFragment = ActorSDK.sharedActor().getDelegate().fragmentForAutocomplete(peer); if (autocompleteFragment == null) { autocompleteFragment = AutocompleteFragment.create(peer); + autocompleteFragment.setUnderlyingView(res.findViewById(R.id.messagesFragment)); } QuoteFragment quoteFragment = ActorSDK.sharedActor().getDelegate().fragmentForQuote(); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/mentions/AutocompleteFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/mentions/AutocompleteFragment.java index ff37afe9b0..4af5ec91a3 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/mentions/AutocompleteFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/mentions/AutocompleteFragment.java @@ -1,16 +1,17 @@ package im.actor.sdk.controllers.conversation.mentions; +import android.graphics.Color; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.view.Gravity; import android.view.LayoutInflater; +import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.animation.Animation; import android.view.animation.Transformation; import android.widget.AbsListView; -import android.widget.AdapterView; import android.widget.FrameLayout; import im.actor.core.entity.BotCommand; @@ -20,6 +21,7 @@ import im.actor.sdk.controllers.BaseFragment; import im.actor.sdk.util.Screen; import im.actor.sdk.view.MaterialInterpolator; +import im.actor.sdk.view.adapters.BottomSheetListView; import im.actor.sdk.view.adapters.HolderAdapter; import im.actor.sdk.view.adapters.RecyclerListView; @@ -32,7 +34,8 @@ public class AutocompleteFragment extends BaseFragment { private boolean isGroup; private HolderAdapter autocompleteAdapter; - private RecyclerListView autocompleteList; + private BottomSheetListView autocompleteList; + private View underlyingView; public static AutocompleteFragment create(Peer peer) { AutocompleteFragment res = new AutocompleteFragment(); @@ -63,10 +66,13 @@ public void onCreate(Bundle saveInstance) { @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - autocompleteList = new RecyclerListView(getContext()); + autocompleteList = new BottomSheetListView(getContext()); + autocompleteList.setVisibility(View.INVISIBLE); + autocompleteList.setUnderlyingView(underlyingView); autocompleteList.setDivider(null); autocompleteList.setDividerHeight(0); - autocompleteList.setBackgroundColor(style.getMainBackgroundColor()); + autocompleteList.setBackgroundColor(Color.TRANSPARENT); + if (autocompleteAdapter != null) { autocompleteList.setAdapter(autocompleteAdapter); } @@ -88,13 +94,14 @@ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, }); // Initial zero height - FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0); + FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); params.gravity = Gravity.BOTTOM; autocompleteList.setLayoutParams(params); return autocompleteList; } + public void onCurrentWordChanged(String text) { if (isBot) { if (text.startsWith("/")) { @@ -135,24 +142,22 @@ public void onDestroyView() { // Expand Animations // - private void expandMentions(final View v, final int oldRowsCount, final int newRowsCount) { - if (newRowsCount == oldRowsCount) { - return; - } - - v.measure(AbsListView.LayoutParams.MATCH_PARENT, AbsListView.LayoutParams.WRAP_CONTENT); - int newRowsHeight = Screen.dp(48) * newRowsCount + newRowsCount; + private void expandMentions(final BottomSheetListView list, final int oldRowsCount, final int newRowsCount) { + list.post(() -> { + if (newRowsCount == oldRowsCount) { + return; + } - final int targetHeight = (newRowsHeight) > Screen.dp(96 + 2) ? Screen.dp(122) : newRowsHeight; - final int initialHeight = v.getLayoutParams().height; - v.getLayoutParams().height = initialHeight; - v.setVisibility(View.VISIBLE); - Animation a = new ExpandAnimation(v, targetHeight, initialHeight); + list.setMinHeight(newRowsCount == 1 ? Screen.dp(48) : newRowsCount == 2 ? Screen.dp(97) : Screen.dp(122)); + list.setVisibility(View.VISIBLE); +// Animation a = new ExpandAnimation(list, targetHeight, initialHeight); +// +// a.setDuration((newRowsCount > oldRowsCount ? targetHeight : initialHeight / Screen.dp(1))); +// a.setInterpolator(MaterialInterpolator.getInstance()); +// list.startAnimation(a); + }); - a.setDuration((newRowsCount > oldRowsCount ? targetHeight : initialHeight / Screen.dp(1))); - a.setInterpolator(MaterialInterpolator.getInstance()); - v.startAnimation(a); } private static class ExpandAnimation extends Animation { @@ -188,4 +193,8 @@ public boolean willChangeBounds() { return true; } } + + public void setUnderlyingView(View underlyingView) { + this.underlyingView = underlyingView; + } } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/mentions/MentionsAdapter.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/mentions/MentionsAdapter.java index 1dfb7f5140..44600b4c03 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/mentions/MentionsAdapter.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/mentions/MentionsAdapter.java @@ -81,6 +81,7 @@ private class GroupViewHolder extends ViewHolder { @Override public View init(final MentionFilterResult data, ViewGroup viewGroup, Context context) { View res = ((Activity) context).getLayoutInflater().inflate(R.layout.fragment_chat_mention_item, viewGroup, false); + res.setBackgroundResource(R.drawable.selectable_background); res.findViewById(R.id.divider).setBackgroundColor(ActorSDK.sharedActor().style.getDividerColor()); userName = (TextView) res.findViewById(R.id.name); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/adapters/BottomSheetListView.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/adapters/BottomSheetListView.java new file mode 100644 index 0000000000..c964ad0f11 --- /dev/null +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/adapters/BottomSheetListView.java @@ -0,0 +1,115 @@ +package im.actor.sdk.view.adapters; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AbsListView; + +public class BottomSheetListView extends RecyclerListView { + + private View header; + private View underlyingView; + private int minHeight = 0; + + public BottomSheetListView(Context context) { + super(context); + init(); + } + + public BottomSheetListView(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + public BottomSheetListView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(); + } + + public BottomSheetListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + init(); + } + + + @Override + public int getCount() { + return super.getCount() - 1; + } + + private void init() { + header = new View(getContext()); + header.setLayoutParams(new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0)); + addHeaderView(header); + + setOnTouchListener(new View.OnTouchListener() { + boolean delegateTouch = false; + + @Override + public boolean onTouch(View view, MotionEvent motionEvent) { + boolean stopDelegate = false; + switch (motionEvent.getAction()) { + case MotionEvent.ACTION_DOWN: + delegateTouch = isWithinHeaderBounds(motionEvent.getRawX(), motionEvent.getRawY()); + break; + + case MotionEvent.ACTION_UP: + stopDelegate = true; + } + + if (delegateTouch && underlyingView != null) { + delegateTouch = !stopDelegate; + underlyingView.dispatchTouchEvent(motionEvent); + return true; + } + + + return false; + } + }); + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + resizeHeader(); + } + + protected void resizeHeader() { + if (header.getLayoutParams().height != getHeight() - minHeight) { + header.getLayoutParams().height = getHeight() - minHeight; + header.requestLayout(); + } + } + + public void setMinHeight(int minHeight) { + this.minHeight = minHeight; + resizeHeader(); + } + + public View getHeader() { + return header; + } + + boolean isWithinHeaderBounds(float xPoint, float yPoint) { + int[] l = new int[2]; + header.getLocationOnScreen(l); + int x = l[0]; + int y = l[1]; + int w = header.getWidth(); + int h = header.getHeight(); + + if (xPoint < x || xPoint > x + w || yPoint < y || yPoint > y + h) { + return false; + } + return true; + } + + public void setUnderlyingView(View underlyingView) { + this.underlyingView = underlyingView; + } + + +} diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable/selectable_background.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable/selectable_background.xml new file mode 100644 index 0000000000..6ed6f76a17 --- /dev/null +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable/selectable_background.xml @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/colors.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/colors.xml index 51eba4a6c7..9a50e6d8c5 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/colors.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/colors.xml @@ -60,6 +60,10 @@ #44000000 #66000000 + #ffffff + #999999 + #dddddd + From eb17de50f97148177f7200f1c05ef0c82d890619 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Fri, 2 Sep 2016 21:48:25 +0300 Subject: [PATCH 320/414] style(android): disable autocomplete overscroll/scrollbar --- .../java/im/actor/sdk/view/adapters/BottomSheetListView.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/adapters/BottomSheetListView.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/adapters/BottomSheetListView.java index c964ad0f11..f43c5f2ca2 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/adapters/BottomSheetListView.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/adapters/BottomSheetListView.java @@ -40,6 +40,8 @@ public int getCount() { } private void init() { + setOverScrollMode(OVER_SCROLL_NEVER); + setVerticalScrollBarEnabled(false); header = new View(getContext()); header.setLayoutParams(new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0)); addHeaderView(header); From 14ffeb9685fd56581fcc5da9c746ada52d914ace Mon Sep 17 00:00:00 2001 From: kor_ka Date: Mon, 5 Sep 2016 14:54:28 +0300 Subject: [PATCH 321/414] style(android): input bar move shadow above autocomplete --- .../mentions/AutocompleteFragment.java | 2 +- .../view/adapters/BottomSheetListView.java | 19 +++++++++++++++++-- .../src/main/res/layout/fragment_chat.xml | 14 +++++++------- 3 files changed, 25 insertions(+), 10 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/mentions/AutocompleteFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/mentions/AutocompleteFragment.java index 4af5ec91a3..7f3d850876 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/mentions/AutocompleteFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/mentions/AutocompleteFragment.java @@ -149,7 +149,7 @@ private void expandMentions(final BottomSheetListView list, final int oldRowsCou } - list.setMinHeight(newRowsCount == 1 ? Screen.dp(48) : newRowsCount == 2 ? Screen.dp(97) : Screen.dp(122)); + list.setMinHeight(newRowsCount == 0 ? 0 : newRowsCount == 1 ? Screen.dp(48) + 1 : newRowsCount == 2 ? Screen.dp(96) + 2 : Screen.dp(122)); list.setVisibility(View.VISIBLE); // Animation a = new ExpandAnimation(list, targetHeight, initialHeight); // diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/adapters/BottomSheetListView.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/adapters/BottomSheetListView.java index f43c5f2ca2..c7069c95e3 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/adapters/BottomSheetListView.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/adapters/BottomSheetListView.java @@ -2,14 +2,22 @@ import android.content.Context; import android.util.AttributeSet; +import android.view.Gravity; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.AbsListView; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.LinearLayout; + +import im.actor.sdk.ActorSDK; +import im.actor.sdk.R; +import im.actor.sdk.util.Screen; public class BottomSheetListView extends RecyclerListView { - private View header; + private FrameLayout header; private View underlyingView; private int minHeight = 0; @@ -42,8 +50,14 @@ public int getCount() { private void init() { setOverScrollMode(OVER_SCROLL_NEVER); setVerticalScrollBarEnabled(false); - header = new View(getContext()); + header = new FrameLayout(getContext()); +// header.setBackgroundColor(ActorSDK.sharedActor().style.getAccentColor()); header.setLayoutParams(new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0)); + ImageView shadow = new ImageView(getContext()); + shadow.setScaleType(ImageView.ScaleType.FIT_XY); + shadow.setImageResource(R.drawable.conv_field_shadow); + FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, Screen.dp(2), Gravity.BOTTOM); + header.addView(shadow, params); addHeaderView(header); setOnTouchListener(new View.OnTouchListener() { @@ -80,6 +94,7 @@ protected void onSizeChanged(int w, int h, int oldw, int oldh) { } protected void resizeHeader() { + setVisibility(minHeight == 0 ? INVISIBLE : VISIBLE); if (header.getLayoutParams().height != getHeight() - minHeight) { header.getLayoutParams().height = getHeight() - minHeight; header.requestLayout(); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_chat.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_chat.xml index 07fda5a107..4b8605e331 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_chat.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_chat.xml @@ -15,6 +15,13 @@ android:layout_height="match_parent" android:layout_above="@+id/sendContainer" /> + + - - Date: Mon, 5 Sep 2016 15:53:44 +0300 Subject: [PATCH 322/414] style(android): recover commands/mentions selector --- .../controllers/conversation/mentions/CommandsAdapter.java | 2 ++ .../controllers/conversation/mentions/MentionsAdapter.java | 3 ++- .../java/im/actor/sdk/view/adapters/BottomSheetListView.java | 5 ++++- .../src/main/res/layout/fragment_chat_mention_item.xml | 1 + 4 files changed, 9 insertions(+), 2 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/mentions/CommandsAdapter.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/mentions/CommandsAdapter.java index fbc8a13675..db5b0a565d 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/mentions/CommandsAdapter.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/mentions/CommandsAdapter.java @@ -90,6 +90,8 @@ public class CommandHolder extends ViewHolder { @Override public View init(final BotCommand data, ViewGroup viewGroup, Context context) { View res = ((Activity) context).getLayoutInflater().inflate(R.layout.fragment_chat_mention_item, viewGroup, false); + res.setBackgroundColor(ActorSDK.sharedActor().style.getMainBackgroundColor()); + res.findViewById(R.id.container).setBackgroundResource(R.drawable.selector); res.findViewById(R.id.divider).setBackgroundColor(ActorSDK.sharedActor().style.getDividerColor()); commandName = (TextView) res.findViewById(R.id.name); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/mentions/MentionsAdapter.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/mentions/MentionsAdapter.java index 44600b4c03..feebe0480d 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/mentions/MentionsAdapter.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/mentions/MentionsAdapter.java @@ -81,7 +81,8 @@ private class GroupViewHolder extends ViewHolder { @Override public View init(final MentionFilterResult data, ViewGroup viewGroup, Context context) { View res = ((Activity) context).getLayoutInflater().inflate(R.layout.fragment_chat_mention_item, viewGroup, false); - res.setBackgroundResource(R.drawable.selectable_background); + res.setBackgroundColor(ActorSDK.sharedActor().style.getMainBackgroundColor()); + res.findViewById(R.id.container).setBackgroundResource(R.drawable.selector); res.findViewById(R.id.divider).setBackgroundColor(ActorSDK.sharedActor().style.getDividerColor()); userName = (TextView) res.findViewById(R.id.name); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/adapters/BottomSheetListView.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/adapters/BottomSheetListView.java index c7069c95e3..8dd194453d 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/adapters/BottomSheetListView.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/adapters/BottomSheetListView.java @@ -1,6 +1,8 @@ package im.actor.sdk.view.adapters; import android.content.Context; +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; import android.util.AttributeSet; import android.view.Gravity; import android.view.MotionEvent; @@ -48,6 +50,7 @@ public int getCount() { } private void init() { + setSelector(new ColorDrawable(Color.TRANSPARENT)); setOverScrollMode(OVER_SCROLL_NEVER); setVerticalScrollBarEnabled(false); header = new FrameLayout(getContext()); @@ -94,7 +97,7 @@ protected void onSizeChanged(int w, int h, int oldw, int oldh) { } protected void resizeHeader() { - setVisibility(minHeight == 0 ? INVISIBLE : VISIBLE); + setVisibility(minHeight == 0 ? GONE : VISIBLE); if (header.getLayoutParams().height != getHeight() - minHeight) { header.getLayoutParams().height = getHeight() - minHeight; header.requestLayout(); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_chat_mention_item.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_chat_mention_item.xml index 8e68a6da17..952c25d8cb 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_chat_mention_item.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_chat_mention_item.xml @@ -10,6 +10,7 @@ android:orientation="vertical"> Date: Mon, 5 Sep 2016 17:04:21 +0300 Subject: [PATCH 323/414] fix(android): autocomplete on click position --- .../im/actor/sdk/view/adapters/BottomSheetListView.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/adapters/BottomSheetListView.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/adapters/BottomSheetListView.java index 8dd194453d..03bbadf7f8 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/adapters/BottomSheetListView.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/adapters/BottomSheetListView.java @@ -9,6 +9,7 @@ import android.view.View; import android.view.ViewGroup; import android.widget.AbsListView; +import android.widget.AdapterView; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.LinearLayout; @@ -131,5 +132,13 @@ public void setUnderlyingView(View underlyingView) { this.underlyingView = underlyingView; } + @Override + public void setOnItemClickListener(OnItemClickListener listener) { + super.setOnItemClickListener((adapterView, view, i, l) -> listener.onItemClick(adapterView, view, i - 1, l)); + } + @Override + public void setOnItemLongClickListener(OnItemLongClickListener listener) { + super.setOnItemLongClickListener((adapterView, view, i, l) -> listener.onItemLongClick(adapterView, view, i - 1, l)); + } } From aef4f2ba4289db5eb3d1bf561ebf230b8c46c40e Mon Sep 17 00:00:00 2001 From: kor_ka Date: Tue, 6 Sep 2016 14:52:47 +0300 Subject: [PATCH 324/414] fix(core): apply inMaxDate and inReadDate separately --- .../modules/messaging/router/RouterActor.java | 35 +++++++++++++------ 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java index 1338ef2ccd..88f3ee8928 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java @@ -215,6 +215,7 @@ private Promise onNewMessages(Peer peer, List messages) { ConversationState state = conversationStates.getValue(peer.getUnuqueId()); Message topMessage = null; int unreadCount = 0; + long maxInReadDate = 0; long maxInDate = 0; for (Message m : messages) { if (topMessage == null || topMessage.getSortDate() < m.getSortDate()) { @@ -223,6 +224,9 @@ private Promise onNewMessages(Peer peer, List messages) { if (m.getSenderId() != myUid()) { if (m.getSortDate() > state.getInReadDate()) { unreadCount++; + maxInReadDate = Math.max(maxInReadDate, m.getSortDate()); + } + if (m.getSortDate() > state.getInMaxMessageDate()) { maxInDate = Math.max(maxInDate, m.getSortDate()); } } @@ -247,24 +251,33 @@ private Promise onNewMessages(Peer peer, List messages) { if (unreadCount != 0) { if (isConversationVisible) { // Auto Reading message - if (maxInDate > 0) { - if (state.getInReadDate() < maxInDate) { - state = state.changeInReadDate(maxInDate); + boolean needUpdateState = false; + if (maxInReadDate > 0) { + if (state.getInReadDate() < maxInReadDate) { + state = state.changeInReadDate(maxInReadDate); } state = state.changeCounter(0); - if (state.getInMaxMessageDate() < maxInDate) { - state.changeInMaxDate(maxInDate); - } + context().getMessagesModule().getPlainReadActor() - .send(new CursorReaderActor.MarkRead(peer, maxInDate)); - context().getNotificationsModule().onOwnRead(peer, maxInDate); + .send(new CursorReaderActor.MarkRead(peer, maxInReadDate)); + context().getNotificationsModule().onOwnRead(peer, maxInReadDate); isRead = true; + needUpdateState = true; + } + + if (state.getInMaxMessageDate() < maxInDate) { + state.changeInMaxDate(maxInDate); + needUpdateState = true; + } + + if (needUpdateState) { conversationStates.addOrUpdateItem(state); } + } else { // Updating counter state = state.changeCounter(state.getUnreadCount() + unreadCount); - if (maxInDate > state.getInMaxMessageDate()) { + if (state.getInMaxMessageDate() < maxInDate) { state = state .changeInMaxDate(maxInDate); } @@ -278,9 +291,9 @@ private Promise onNewMessages(Peer peer, List messages) { // // Marking As Received // - if (maxInDate > 0 && !isRead) { + if (maxInReadDate > 0 && !isRead) { context().getMessagesModule().getPlainReceiverActor() - .send(new CursorReceiverActor.MarkReceived(peer, maxInDate)); + .send(new CursorReceiverActor.MarkReceived(peer, maxInReadDate)); } From 8d890bd35cd588da5d52b8eed0d5a9e2cb1910db Mon Sep 17 00:00:00 2001 From: kor_ka Date: Tue, 6 Sep 2016 15:18:34 +0300 Subject: [PATCH 325/414] chore(core): remove tls endpoints, remove ip from endpoints --- .../android-sdk/src/main/java/im/actor/sdk/ActorSDK.java | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorSDK.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorSDK.java index 47a91d29ef..08bc1904aa 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorSDK.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorSDK.java @@ -198,12 +198,9 @@ public class ActorSDK { private ActorSDK() { endpoints = new String[]{ - "tls://front1-mtproto-api-rev2.actor.im@104.155.30.208", - "tls://front2-mtproto-api-rev2.actor.im@104.155.30.208", - - "tcp://front1-mtproto-api-rev3.actor.im@104.155.30.208:443", - "tcp://front2-mtproto-api-rev3.actor.im@104.155.30.208:443", - "tcp://front3-mtproto-api-rev3.actor.im@104.155.30.208:443" + "tcp://front1-mtproto-api-rev3.actor.im:443", + "tcp://front2-mtproto-api-rev3.actor.im:443", + "tcp://front3-mtproto-api-rev3.actor.im:443" }; trustedKeys = new String[]{ "d9d34ed487bd5b434eda2ef2c283db587c3ae7fb88405c3834d9d1a6d247145b", From 67bea55202a677c8b1407d10fc4b8586ad901d39 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Tue, 6 Sep 2016 18:13:32 +0300 Subject: [PATCH 326/414] style(core): fix profile action button top padding --- .../src/main/res/layout/fragment_profile.xml | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_profile.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_profile.xml index f663128f3a..8da0e0727d 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_profile.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_profile.xml @@ -284,15 +284,21 @@ - + + + + + From a67d5995320f5dfe098c2becce4312a731f9f44b Mon Sep 17 00:00:00 2001 From: kor_ka Date: Tue, 6 Sep 2016 18:54:42 +0300 Subject: [PATCH 327/414] fix(js): add missing isChannel flag to content conversion --- .../im/actor/core/js/entity/JsContent.java | 3 ++- .../im/actor/core/js/entity/JsDialog.java | 13 ++++++++---- .../js/providers/JsNotificationsProvider.java | 21 ++++++++++++------- 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/actor-sdk/sdk-core/core/core-js/src/main/java/im/actor/core/js/entity/JsContent.java b/actor-sdk/sdk-core/core/core-js/src/main/java/im/actor/core/js/entity/JsContent.java index cb2c36ea2e..a527c9552c 100644 --- a/actor-sdk/sdk-core/core/core-js/src/main/java/im/actor/core/js/entity/JsContent.java +++ b/actor-sdk/sdk-core/core/core-js/src/main/java/im/actor/core/js/entity/JsContent.java @@ -30,6 +30,7 @@ public abstract class JsContent extends JavaScriptObject { public static JsContent createContent(AbsContent src, int sender) { + JsMessenger messenger = JsMessenger.getInstance(); JsContent content; if (src instanceof TextContent) { @@ -58,7 +59,7 @@ public static JsContent createContent(AbsContent src, int sender) { content = JsContentText.create(((TextContent) src).getText()); } } else if (src instanceof ServiceContent) { - content = JsContentService.create(messenger.getFormatter().formatFullServiceMessage(sender, (ServiceContent) src)); + content = JsContentService.create(messenger.getFormatter().formatFullServiceMessage(sender, (ServiceContent) src, false)); } else if (src instanceof DocumentContent) { DocumentContent doc = (DocumentContent) src; diff --git a/actor-sdk/sdk-core/core/core-js/src/main/java/im/actor/core/js/entity/JsDialog.java b/actor-sdk/sdk-core/core/core-js/src/main/java/im/actor/core/js/entity/JsDialog.java index caf0e9a2df..337ee7494a 100644 --- a/actor-sdk/sdk-core/core/core-js/src/main/java/im/actor/core/js/entity/JsDialog.java +++ b/actor-sdk/sdk-core/core/core-js/src/main/java/im/actor/core/js/entity/JsDialog.java @@ -8,6 +8,8 @@ import im.actor.core.entity.ContentType; import im.actor.core.entity.Dialog; +import im.actor.core.entity.GroupType; +import im.actor.core.entity.Peer; import im.actor.core.entity.PeerType; import im.actor.core.js.JsMessenger; import im.actor.runtime.js.mvvm.JsEntityConverter; @@ -22,7 +24,8 @@ public JsDialog convert(Dialog src) { JsMessenger messenger = JsMessenger.getInstance(); boolean showSender = false; - if (src.getPeer().getPeerType() == PeerType.GROUP) { + Peer peer = src.getPeer(); + if (peer.getPeerType() == PeerType.GROUP) { if (src.getMessageType() != ContentType.SERVICE && src.getMessageType() != ContentType.NONE) { showSender = src.getSenderId() != 0; } @@ -40,12 +43,14 @@ public JsDialog convert(Dialog src) { fileUrl = messenger.getFileUrl(src.getDialogAvatar().getSmallImage().getFileReference()); } + boolean isChannel = peer.getPeerType() == PeerType.GROUP && messenger.getGroups().get(peer.getPeerId()).getGroupType() == GroupType.CHANNEL; + boolean highlightContent = src.getMessageType() != ContentType.TEXT; String messageText = messenger.getFormatter().formatContentText(src.getSenderId(), - src.getMessageType(), src.getText(), src.getRelatedUid()); + src.getMessageType(), src.getText(), src.getRelatedUid(), false); - JsPeerInfo peerInfo = JsPeerInfo.create(JsPeer.create(src.getPeer()), src.getDialogTitle(), null, fileUrl, - Placeholders.getPlaceholder(src.getPeer().getPeerId()), false); + JsPeerInfo peerInfo = JsPeerInfo.create(JsPeer.create(peer), src.getDialogTitle(), null, fileUrl, + Placeholders.getPlaceholder(peer.getPeerId()), isChannel); String state = "unknown"; if (messenger.myUid() == src.getSenderId()) { diff --git a/actor-sdk/sdk-core/core/core-js/src/main/java/im/actor/core/js/providers/JsNotificationsProvider.java b/actor-sdk/sdk-core/core/core-js/src/main/java/im/actor/core/js/providers/JsNotificationsProvider.java index 4f719e6b5f..af087fd9a9 100644 --- a/actor-sdk/sdk-core/core/core-js/src/main/java/im/actor/core/js/providers/JsNotificationsProvider.java +++ b/actor-sdk/sdk-core/core/core-js/src/main/java/im/actor/core/js/providers/JsNotificationsProvider.java @@ -10,7 +10,9 @@ import im.actor.core.Messenger; import im.actor.core.entity.Avatar; +import im.actor.core.entity.GroupType; import im.actor.core.entity.Notification; +import im.actor.core.entity.Peer; import im.actor.core.entity.PeerType; import im.actor.core.js.entity.JsPeer; import im.actor.core.js.JsMessenger; @@ -50,15 +52,16 @@ public void onNotification(Messenger messenger, List topNotificati Notification notification = topNotifications.get(0); // Peer info + Peer peer = notification.getPeer(); if (conversationsCount == 1) { Avatar peerAvatar; - JsPeer jsPeer = JsPeer.create(notification.getPeer()); - if (notification.getPeer().getPeerType() == PeerType.PRIVATE) { - UserVM userVM = messenger.getUser(notification.getPeer().getPeerId()); + JsPeer jsPeer = JsPeer.create(peer); + if (peer.getPeerType() == PeerType.PRIVATE) { + UserVM userVM = messenger.getUser(peer.getPeerId()); peerTitle = userVM.getName().get(); peerAvatar = userVM.getAvatar().get(); } else { - GroupVM groupVM = messenger.getGroup(notification.getPeer().getPeerId()); + GroupVM groupVM = messenger.getGroup(peer.getPeerId()); peerTitle = groupVM.getName().get(); peerAvatar = groupVM.getAvatar().get(); } @@ -80,19 +83,22 @@ public void onNotification(Messenger messenger, List topNotificati showCounters = true; } + boolean isChannel = peer.getPeerType() == PeerType.GROUP && messenger.getGroups().get(peer.getPeerId()).getGroupType() == GroupType.CHANNEL; + if (conversationsCount == 1) { for (int i = 0; i < nCount; i++) { Notification n = topNotifications.get(i); if (contentMessage.length() > 0) { contentMessage += "\n"; } - if (notification.getPeer().getPeerType() == PeerType.GROUP) { + if (peer.getPeerType() == PeerType.GROUP) { contentMessage += messenger.getUser(notification.getSender()).getName().get() + ": "; } contentMessage += messenger.getFormatter().formatContentText(n.getSender(), n.getContentDescription().getContentType(), n.getContentDescription().getText(), - n.getContentDescription().getRelatedUser()); + n.getContentDescription().getRelatedUser(), + isChannel); } if (showCounters) { @@ -114,7 +120,8 @@ public void onNotification(Messenger messenger, List topNotificati contentMessage += messenger.getFormatter().formatContentText(n.getSender(), n.getContentDescription().getContentType(), n.getContentDescription().getText(), - n.getContentDescription().getRelatedUser()); + n.getContentDescription().getRelatedUser(), + isChannel); } if (showCounters) { From f0383246767eaa30ba325a04767d860c63e73872 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Tue, 6 Sep 2016 21:32:26 +0300 Subject: [PATCH 328/414] style(android): settings fields icons padding --- .../settings/BaseActorSettingsFragment.java | 3 +- .../main/res/layout/actor_settings_field.xml | 6 +-- .../src/main/res/layout/fragment_settings.xml | 39 ++++++------------- 3 files changed, 14 insertions(+), 34 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/BaseActorSettingsFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/BaseActorSettingsFragment.java index f91fb02fb5..025270616d 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/BaseActorSettingsFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/BaseActorSettingsFragment.java @@ -595,8 +595,7 @@ public void onClick(View view) { icon.setImageResource(R.drawable.ic_image_black_24dp); icon.setScaleType(ImageView.ScaleType.CENTER_INSIDE); icon.setColorFilter(ActorSDK.sharedActor().style.getSettingsIconColor(), PorterDuff.Mode.SRC_IN); - icon.setPadding(Screen.dp(16), 0, 0, 0); - fl.addView(icon, new FrameLayout.LayoutParams(Screen.dp(40), Screen.dp(85), Gravity.CENTER_VERTICAL | Gravity.LEFT)); + fl.addView(icon, new FrameLayout.LayoutParams(Screen.dp(72), Screen.dp(85), Gravity.CENTER)); fl.setLayoutParams(new ViewGroup.LayoutParams(Screen.dp(72), Screen.dp(85))); wallpaperAdapter.addHeaderView(fl); wallpapers.setLayoutManager(new LinearLayoutManager(getActivity(), LinearLayoutManager.HORIZONTAL, false)); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/actor_settings_field.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/actor_settings_field.xml index faaf8db974..26083c93a5 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/actor_settings_field.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/actor_settings_field.xml @@ -12,10 +12,8 @@ + android:layout_width="72dp" + android:layout_height="match_parent" /> + android:layout_width="72dp" + android:scaleType="centerInside" + android:layout_height="match_parent" /> Date: Wed, 7 Sep 2016 19:43:36 +0300 Subject: [PATCH 329/414] fix(android): ru tos and privacy policy hilight --- .../android-sdk/src/main/res/values-ru/ui_text.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-ru/ui_text.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-ru/ui_text.xml index d71344dec2..5206282d22 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-ru/ui_text.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-ru/ui_text.xml @@ -78,8 +78,8 @@ Использовать email для регистрации Использовать телефон для регистрации - Политика Конфиденциальности - Условия Использования + Privacy Policy + Terms of Service Использовать альтернативный сервер По умолчанию From a0babe338d08bcfc5824e88ac4c9bbdc1453a7f9 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Wed, 7 Sep 2016 20:36:30 +0300 Subject: [PATCH 330/414] fix(android): share menu rtl support --- .../conversation/attach/AttachFragment.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/attach/AttachFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/attach/AttachFragment.java index 96a6df2ec0..eb2015c3bd 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/attach/AttachFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/attach/AttachFragment.java @@ -121,6 +121,12 @@ private void prepareView() { int shareIconSize = Screen.dp(60); View.OnClickListener defaultSendOcl = null; + Configuration config = getResources().getConfiguration(); + boolean isRtl = false; + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) { + isRtl = config.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; + } + for (int i = 0; i < menuFields.size(); i++) { ShareMenuField f = menuFields.get(i); @@ -155,14 +161,14 @@ private void prepareView() { icon.setOnClickListener(l); FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(menuItemSize, menuItemSize); - params.setMargins(marginFromStart, first ? 0 : secondRowTopMargin, initialMargin, 0); + params.setMargins(isRtl ? initialMargin : marginFromStart, first ? 0 : secondRowTopMargin, isRtl ? marginFromStart : initialMargin, 0); if (i == menuFields.size() - 1) { menuIconToChange = icon; menuTitleToChange = title; defaultSendOcl = l; - params.setMargins(marginFromStart, first ? 0 : secondRowTopMargin, 0, 0); + params.setMargins(isRtl ? 0 : marginFromStart, first ? 0 : secondRowTopMargin, isRtl ? marginFromStart : 0, 0); } row.addView(shareItem, params); From 56915b28208074875d389c5d9b89f9be57a721c3 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Wed, 7 Sep 2016 21:51:23 +0300 Subject: [PATCH 331/414] feat(android): attach menu fast share as bottom sheet --- .../conversation/attach/AttachFragment.java | 122 ++++++++++-------- .../attach/FastAttachAdapter.java | 2 + .../main/res-material/layout/share_menu.xml | 10 +- .../main/res/layout/share_menu_fast_share.xml | 2 +- 4 files changed, 79 insertions(+), 57 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/attach/AttachFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/attach/AttachFragment.java index eb2015c3bd..75694e9829 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/attach/AttachFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/attach/AttachFragment.java @@ -14,6 +14,7 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.content.ContextCompat; +import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.Base64; @@ -42,12 +43,14 @@ import im.actor.sdk.util.SDKFeatures; import im.actor.sdk.util.Screen; import im.actor.sdk.view.ShareMenuButtonFactory; +import im.actor.sdk.view.adapters.HeaderViewRecyclerAdapter; import static im.actor.sdk.util.ActorSDKMessenger.messenger; public class AttachFragment extends AbsAttachFragment implements MediaPickerCallback { private static final int PERMISSION_REQ_MEDIA = 11; + public static final int SPAN_COUNT = 4; private FrameLayout root; private View container; @@ -56,6 +59,7 @@ public class AttachFragment extends AbsAttachFragment implements MediaPickerCall private TextView menuTitleToChange; private boolean isLoaded = false; + private RecyclerView fastShare; public AttachFragment(Peer peer) { super(peer); @@ -90,7 +94,8 @@ private void prepareView() { isLoaded = true; container = getLayoutInflater(null).inflate(R.layout.share_menu, root, false); - container.setVisibility(View.INVISIBLE); + fastShare = new RecyclerView(getActivity()); + fastShare.setVisibility(View.INVISIBLE); container.findViewById(R.id.menu_bg).setBackgroundColor(style.getMainBackgroundColor()); container.findViewById(R.id.cancelField).setOnClickListener(view -> hide()); @@ -192,11 +197,19 @@ private void prepareView() { hide(); }; - RecyclerView fastShare = (RecyclerView) container.findViewById(R.id.fast_share); +// RecyclerView fastShare = (RecyclerView) container.findViewById(R.id.fast_share); fastAttachAdapter = new FastAttachAdapter(getActivity()); - LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity(), LinearLayoutManager.HORIZONTAL, false); - fastShare.setAdapter(fastAttachAdapter); + HeaderViewRecyclerAdapter adapter = new HeaderViewRecyclerAdapter(fastAttachAdapter); + adapter.addHeaderView(container); + GridLayoutManager layoutManager = new GridLayoutManager(getActivity(), SPAN_COUNT); + fastShare.setAdapter(adapter); fastShare.setLayoutManager(layoutManager); + layoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { + @Override + public int getSpanSize(int position) { + return position == 0 ? 4 : 1; + } + }); StateListDrawable background = ShareMenuButtonFactory.get(style.getMainColor(), getActivity()); final View.OnClickListener finalDefaultSendOcl = defaultSendOcl; @@ -218,14 +231,20 @@ private void prepareView() { menuIconToChange.setPadding(0, 0, 0, 0); } }); - - root.addView(container); + root.post(new Runnable() { + @Override + public void run() { + container.getLayoutParams().height = root.getHeight() - Screen.dp(135); + container.requestLayout(); + } + }); + root.addView(fastShare); } @Override public void show() { prepareView(); - if (container.getVisibility() == View.INVISIBLE) { + if (fastShare.getVisibility() == View.INVISIBLE) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { Activity activity = getActivity(); if (activity == null) { @@ -240,58 +259,58 @@ public void show() { } onShown(); messenger().getGalleryScannerActor().send(new GalleryScannerActor.Show()); - showView(container); - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { - View internal = container.findViewById(R.id.menu_bg); - int cx = internal.getWidth() - Screen.dp(56 + 56); - int cy = internal.getHeight() - Screen.dp(56 / 2); - float finalRadius = (float) Math.hypot(cx, cy); - Animator anim = ViewAnimationUtils.createCircularReveal(internal, cx, cy, 0, finalRadius); - anim.setDuration(200); - anim.start(); - internal.setAlpha(1); - } + showView(fastShare); +// if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { +// View internal = container.findViewById(R.id.menu_bg); +// int cx = internal.getWidth() - Screen.dp(56 + 56); +// int cy = internal.getHeight() - Screen.dp(56 / 2); +// float finalRadius = (float) Math.hypot(cx, cy); +// Animator anim = ViewAnimationUtils.createCircularReveal(internal, cx, cy, 0, finalRadius); +// anim.setDuration(200); +// anim.start(); +// internal.setAlpha(1); +// } } } @Override public void hide() { - if (container != null && container.getVisibility() == View.VISIBLE) { + if (fastShare != null && fastShare.getVisibility() == View.VISIBLE) { onHidden(); fastAttachAdapter.clearSelected(); messenger().getGalleryScannerActor().send(new GalleryScannerActor.Hide()); - hideView(container); - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { - View internal = container.findViewById(R.id.menu_bg); - int cx = internal.getWidth() - Screen.dp(56 + 56); - int cy = internal.getHeight() - Screen.dp(56 / 2); - float finalRadius = (float) Math.hypot(cx, cy); - Animator anim = ViewAnimationUtils.createCircularReveal(internal, cx, cy, finalRadius, 0); - anim.addListener(new Animator.AnimatorListener() { - @Override - public void onAnimationStart(Animator animator) { - internal.setAlpha(1); - } - - @Override - public void onAnimationEnd(Animator animator) { - internal.setAlpha(0); - } - - @Override - public void onAnimationCancel(Animator animator) { - - } - - @Override - public void onAnimationRepeat(Animator animator) { - - } - }); - - anim.setDuration(200); - anim.start(); - } + hideView(fastShare); +// if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { +// View internal = container.findViewById(R.id.menu_bg); +// int cx = internal.getWidth() - Screen.dp(56 + 56); +// int cy = internal.getHeight() - Screen.dp(56 / 2); +// float finalRadius = (float) Math.hypot(cx, cy); +// Animator anim = ViewAnimationUtils.createCircularReveal(internal, cx, cy, finalRadius, 0); +// anim.addListener(new Animator.AnimatorListener() { +// @Override +// public void onAnimationStart(Animator animator) { +// internal.setAlpha(1); +// } +// +// @Override +// public void onAnimationEnd(Animator animator) { +// internal.setAlpha(0); +// } +// +// @Override +// public void onAnimationCancel(Animator animator) { +// +// } +// +// @Override +// public void onAnimationRepeat(Animator animator) { +// +// } +// }); +// +// anim.setDuration(200); +// anim.start(); +// } } } @@ -411,6 +430,7 @@ public void onDestroyView() { fastAttachAdapter = null; } container = null; + fastShare = null; root = null; menuIconToChange = null; menuTitleToChange = null; diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/attach/FastAttachAdapter.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/attach/FastAttachAdapter.java index 9e75d4b78f..41b8db5e39 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/attach/FastAttachAdapter.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/attach/FastAttachAdapter.java @@ -23,6 +23,7 @@ import java.util.Set; import im.actor.runtime.mvvm.ValueModel; +import im.actor.sdk.ActorSDK; import im.actor.sdk.R; import im.actor.sdk.controllers.ActorBinder; import im.actor.sdk.util.Screen; @@ -82,6 +83,7 @@ public class FastShareVH extends RecyclerView.ViewHolder { public FastShareVH(View itemView) { super(itemView); + itemView.setBackgroundColor(ActorSDK.sharedActor().style.getMainBackgroundColor()); v = (SimpleDraweeView) itemView.findViewById(R.id.image); chb = (CheckBox) itemView.findViewById(R.id.check); int size = Screen.dp(80); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res-material/layout/share_menu.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res-material/layout/share_menu.xml index 2daec0fac1..b3bde0fb5b 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res-material/layout/share_menu.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res-material/layout/share_menu.xml @@ -13,14 +13,14 @@ - + + + + Date: Fri, 9 Sep 2016 14:42:51 +0300 Subject: [PATCH 332/414] fix(android): recover "send:" link --- .../messages/content/TextHolder.java | 1 + .../sdk/view/markdown/AndroidMarkdown.java | 19 ++++--------------- .../android-sdk/src/main/res/values/ids.xml | 1 + 3 files changed, 6 insertions(+), 15 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/TextHolder.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/TextHolder.java index 2a459689c0..bb38af7ec4 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/TextHolder.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/TextHolder.java @@ -72,6 +72,7 @@ protected void bindData(final Message message, long readDate, long receiveDate, } public void bindRawText(CharSequence rawText, long readDate, long receiveDate, Spannable reactions, Message message, boolean isItalic) { + text.setTag(R.id.peer, getPeer()); if (message.getSenderId() == myUid()) { messageBubble.setBackgroundResource(R.drawable.bubble_text_out); } else { diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/markdown/AndroidMarkdown.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/markdown/AndroidMarkdown.java index 18119b7744..5e5bfb8257 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/markdown/AndroidMarkdown.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/markdown/AndroidMarkdown.java @@ -24,9 +24,11 @@ import android.view.View; import android.widget.Toast; +import im.actor.core.entity.Peer; import im.actor.runtime.actors.ActorContext; import im.actor.sdk.ActorSDK; import im.actor.sdk.R; +import im.actor.sdk.controllers.activity.BaseActivity; import im.actor.sdk.controllers.conversation.ChatActivity; import im.actor.sdk.controllers.fragment.preview.CodePreviewActivity; import im.actor.runtime.android.AndroidContext; @@ -119,11 +121,8 @@ private static void writeText(MDText[] texts, SpannableStringBuilder builder) { @Override public void onClick(View view) { Context ctx = view.getContext(); - if (url.getUrl().startsWith("send:")) { - ctx = extractContext(ctx); -// if (ctx instanceof ChatActivity) { -// ActorSDK.sharedActor().getMessenger().sendMessage(((ChatActivity) ctx).getPeer(), url.getUrl().replace("send:", "")); -// } + if (url.getUrl().startsWith("send:") && view.getTag(R.id.peer) != null && view.getTag(R.id.peer) instanceof Peer) { + ActorSDK.sharedActor().getMessenger().sendMessage((Peer) view.getTag(R.id.peer), url.getUrl().replaceFirst("send:", "")); } else { Intent intent = buildChromeIntent().intent; intent.setData(Uri.parse(url.getUrl())); @@ -149,16 +148,6 @@ public void onClick(View view) { } } - private static Context extractContext(Context ctx) { - if (ctx instanceof AppCompatActivity) { - return ctx; - } else if (ctx instanceof ContextWrapper) { - return extractContext(((ContextWrapper) ctx).getBaseContext()); - } - - return ctx; - } - public static CustomTabsIntent buildChromeIntent() { CustomTabsIntent.Builder customTabsIntent = new CustomTabsIntent.Builder(); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ids.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ids.xml index f094b04213..126145cee3 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ids.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ids.xml @@ -8,4 +8,5 @@ + \ No newline at end of file From 5d0fa1252163c06da6ba4d5f4dddd43f74070be5 Mon Sep 17 00:00:00 2001 From: rockjam Date: Wed, 7 Sep 2016 23:15:24 +0300 Subject: [PATCH 333/414] chore(server): docker build setup --- actor-server/Dockerfile | 7 ++++++ actor-server/build.sbt | 2 ++ actor-server/docker.sh | 3 +++ .../docker => var/lib/actor}/conf/logback.xml | 4 +--- .../docker => var/lib/actor}/conf/server.conf | 24 +++++++++---------- .../src/docker/var/lib/actor/files/init | 0 .../usr/share/actor}/conf/server.conf.example | 0 .../actor}/conf/server.conf.example-minimal | 0 8 files changed, 24 insertions(+), 16 deletions(-) create mode 100644 actor-server/Dockerfile create mode 100755 actor-server/docker.sh rename actor-server/src/docker/{opt/docker => var/lib/actor}/conf/logback.xml (87%) rename actor-server/src/docker/{opt/docker => var/lib/actor}/conf/server.conf (63%) delete mode 100644 actor-server/src/docker/var/lib/actor/files/init rename actor-server/src/{universal => linux/usr/share/actor}/conf/server.conf.example (100%) rename actor-server/src/{universal => linux/usr/share/actor}/conf/server.conf.example-minimal (100%) diff --git a/actor-server/Dockerfile b/actor-server/Dockerfile new file mode 100644 index 0000000000..4025021928 --- /dev/null +++ b/actor-server/Dockerfile @@ -0,0 +1,7 @@ +FROM openjdk:8u92-jre-alpine +MAINTAINER Actor LLC +RUN apk --update add bash openssl apr +ADD target/docker/stage/var /var +ENTRYPOINT bin/actor +WORKDIR /var/lib/actor +EXPOSE 9070 9080 9090 diff --git a/actor-server/build.sbt b/actor-server/build.sbt index 0f290c6065..8f24fd276c 100644 --- a/actor-server/build.sbt +++ b/actor-server/build.sbt @@ -2,3 +2,5 @@ addCommandAlias("debianPackage", "debian:packageBin") addCommandAlias("debianPackageSystemd", "; set serverLoading in Debian := com.typesafe.sbt.packager.archetypes.ServerLoader.Systemd ;debian:packageBin" ) + +defaultLinuxInstallLocation in Docker := "/var/lib/actor" diff --git a/actor-server/docker.sh b/actor-server/docker.sh new file mode 100755 index 0000000000..11c3eef5ac --- /dev/null +++ b/actor-server/docker.sh @@ -0,0 +1,3 @@ +#! /bin/bash + +sbt docker:stage && docker build --no-cache=true -f Dockerfile -t actor-server . diff --git a/actor-server/src/docker/opt/docker/conf/logback.xml b/actor-server/src/docker/var/lib/actor/conf/logback.xml similarity index 87% rename from actor-server/src/docker/opt/docker/conf/logback.xml rename to actor-server/src/docker/var/lib/actor/conf/logback.xml index 762dabf245..a97393d26d 100644 --- a/actor-server/src/docker/opt/docker/conf/logback.xml +++ b/actor-server/src/docker/var/lib/actor/conf/logback.xml @@ -3,7 +3,6 @@ - @@ -11,7 +10,6 @@ - true @@ -22,7 +20,7 @@ - + diff --git a/actor-server/src/docker/opt/docker/conf/server.conf b/actor-server/src/docker/var/lib/actor/conf/server.conf similarity index 63% rename from actor-server/src/docker/opt/docker/conf/server.conf rename to actor-server/src/docker/var/lib/actor/conf/server.conf index 0a45894e55..cf9156ad89 100644 --- a/actor-server/src/docker/opt/docker/conf/server.conf +++ b/actor-server/src/docker/var/lib/actor/conf/server.conf @@ -9,38 +9,35 @@ modules { services { postgresql { host: "postgres" - host: ${?DB_HOST} + host: ${?ACTOR_DB_HOST} db: postgres - db: ${?DB_NAME} + db: ${?ACTOR_DB_NAME} user: "postgres" - user: ${?DB_USER} + user: ${?ACTOR_DB_USER} password: "" - password: ${?DB_PASSWORD} + password: ${?ACTOR_DB_PASSWORD} } actor-activation { - uri: "https://activation-gw.actor.im" - auth-token: "FPEinjrmxsq1ZDyu1bc7" - auth-token: ${?ACTIVATION_GW_TOKEN} + uri: "https://gate.actor.im" + auth-token: ${?ACTOR_GATE_TOKEN} } file-storage { location: "/var/lib/actor/files" - location: ${?FILESTORAGE_LOCATION} + location: ${?ACTOR_FILESTORAGE_LOCATION} } } -http { - static-files-directory: "/opt/docker/files" -} - -secret: ${?SECRET} +secret: ${?ACTOR_SECRET} akka { log-config-on-start: true + loggers: ["akka.event.slf4j.Slf4jLogger"] + loglevel: "DEBUG" cluster { seed-nodes = ["akka.tcp://actor-server@127.0.0.1:2552"] @@ -50,3 +47,4 @@ akka { netty.tcp.hostname = "127.0.0.1" } } + diff --git a/actor-server/src/docker/var/lib/actor/files/init b/actor-server/src/docker/var/lib/actor/files/init deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/actor-server/src/universal/conf/server.conf.example b/actor-server/src/linux/usr/share/actor/conf/server.conf.example similarity index 100% rename from actor-server/src/universal/conf/server.conf.example rename to actor-server/src/linux/usr/share/actor/conf/server.conf.example diff --git a/actor-server/src/universal/conf/server.conf.example-minimal b/actor-server/src/linux/usr/share/actor/conf/server.conf.example-minimal similarity index 100% rename from actor-server/src/universal/conf/server.conf.example-minimal rename to actor-server/src/linux/usr/share/actor/conf/server.conf.example-minimal From 233ae9ce9436feeba6c7e576b905382dd9125b87 Mon Sep 17 00:00:00 2001 From: rockjam Date: Fri, 2 Sep 2016 21:57:31 +0300 Subject: [PATCH 334/414] refactor(server): db schema cleanup --- .../dialog/DialogProcessorMigration.scala | 3 +- .../server/dialog/DialogRootMigration.scala | 3 +- .../server/model/AuthSmsCodeObsolete.scala | 4 -- .../im/actor/server/model/Department.scala | 6 -- .../actor/server/model/FileSourceBlock.scala | 3 - .../scala/im/actor/server/model/Manager.scala | 4 -- .../im/actor/server/model/MessageState.scala | 28 -------- .../actor/server/model/UserDepartment.scala | 3 - .../im/actor/server/model/UserPublicKey.scala | 8 --- .../server/model/voximplant/VoxUser.scala | 3 - .../V20160902182358__SchemaCleanup.sql | 14 ++++ .../persist/AuthSmsCodeObsoleteRepo.scala | 31 --------- .../actor/server/persist/DepartmentRepo.scala | 43 ------------ .../server/persist/HistoryMessageRepo.scala | 1 - .../im/actor/server/persist/ManagerRepo.scala | 27 -------- .../persist/MessageStateColumnType.scala | 9 --- .../server/persist/UserDepartmentRepo.scala | 23 ------- .../actor/server/persist/UserPhoneRepo.scala | 13 +--- .../server/persist/UserPublicKeyRepo.scala | 51 -------------- .../server/persist/dialog/DialogRepo.scala | 68 +------------------ .../encryption/EphermalPublicKeyRepo.scala | 2 +- .../server/persist/voximplant/VoxUser.scala | 27 -------- .../persist/webrtc/WebrtcCallRepo.scala | 28 -------- 23 files changed, 24 insertions(+), 378 deletions(-) delete mode 100644 actor-server/actor-models/src/main/scala/im/actor/server/model/AuthSmsCodeObsolete.scala delete mode 100644 actor-server/actor-models/src/main/scala/im/actor/server/model/Department.scala delete mode 100644 actor-server/actor-models/src/main/scala/im/actor/server/model/FileSourceBlock.scala delete mode 100644 actor-server/actor-models/src/main/scala/im/actor/server/model/Manager.scala delete mode 100644 actor-server/actor-models/src/main/scala/im/actor/server/model/MessageState.scala delete mode 100644 actor-server/actor-models/src/main/scala/im/actor/server/model/UserDepartment.scala delete mode 100644 actor-server/actor-models/src/main/scala/im/actor/server/model/UserPublicKey.scala delete mode 100644 actor-server/actor-models/src/main/scala/im/actor/server/model/voximplant/VoxUser.scala create mode 100644 actor-server/actor-persist/src/main/resources/sql/migration/V20160902182358__SchemaCleanup.sql delete mode 100644 actor-server/actor-persist/src/main/scala/im/actor/server/persist/AuthSmsCodeObsoleteRepo.scala delete mode 100644 actor-server/actor-persist/src/main/scala/im/actor/server/persist/DepartmentRepo.scala delete mode 100644 actor-server/actor-persist/src/main/scala/im/actor/server/persist/ManagerRepo.scala delete mode 100644 actor-server/actor-persist/src/main/scala/im/actor/server/persist/MessageStateColumnType.scala delete mode 100644 actor-server/actor-persist/src/main/scala/im/actor/server/persist/UserDepartmentRepo.scala delete mode 100644 actor-server/actor-persist/src/main/scala/im/actor/server/persist/UserPublicKeyRepo.scala delete mode 100644 actor-server/actor-persist/src/main/scala/im/actor/server/persist/voximplant/VoxUser.scala delete mode 100644 actor-server/actor-persist/src/main/scala/im/actor/server/persist/webrtc/WebrtcCallRepo.scala diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogProcessorMigration.scala b/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogProcessorMigration.scala index d2a82d2678..98597997b4 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogProcessorMigration.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogProcessorMigration.scala @@ -5,6 +5,7 @@ import java.time.Instant import akka.actor.Status import akka.pattern.pipe import akka.persistence.SnapshotMetadata +import com.github.ghik.silencer.silent import im.actor.server.cqrs.{ Event, Processor } import im.actor.server.db.DbExtension import im.actor.server.model.{ DialogObsolete, Peer } @@ -80,7 +81,7 @@ trait DialogProcessorMigration extends Processor[DialogState] { private def migrate(): Unit = { log.warning("Starting migration") context become migrating - (db.run(DialogRepo.findDialog(userId, peer)) map { + (db.run(DialogRepo.findDialog(userId, peer): @silent) map { case Some(model) ⇒ model case _ ⇒ PersistEvents(List(Initialized())) }) pipeTo self diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogRootMigration.scala b/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogRootMigration.scala index 2705ba73ee..16ed9dced9 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogRootMigration.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogRootMigration.scala @@ -5,6 +5,7 @@ import java.time.Instant import akka.actor.Status import akka.pattern.pipe import akka.persistence.SnapshotMetadata +import com.github.ghik.silencer.silent import im.actor.server.cqrs.{ Event, Processor } import im.actor.server.db.DbExtension import im.actor.server.model.DialogObsolete @@ -58,7 +59,7 @@ trait DialogRootMigration extends Processor[DialogRootState] { context.become(migrating) (for { - models ← DbExtension(context.system).db.run(DialogRepo.fetchDialogs(userId)) + models ← DbExtension(context.system).db.run(DialogRepo.fetchDialogs(userId): @silent) } yield CreateEvents(models)) pipeTo self } diff --git a/actor-server/actor-models/src/main/scala/im/actor/server/model/AuthSmsCodeObsolete.scala b/actor-server/actor-models/src/main/scala/im/actor/server/model/AuthSmsCodeObsolete.scala deleted file mode 100644 index 1c34d0c953..0000000000 --- a/actor-server/actor-models/src/main/scala/im/actor/server/model/AuthSmsCodeObsolete.scala +++ /dev/null @@ -1,4 +0,0 @@ -package im.actor.server.model - -@SerialVersionUID(1L) -case class AuthSmsCodeObsolete(id: Long, phoneNumber: Long, smsHash: String, smsCode: String, isDeleted: Boolean = false) \ No newline at end of file diff --git a/actor-server/actor-models/src/main/scala/im/actor/server/model/Department.scala b/actor-server/actor-models/src/main/scala/im/actor/server/model/Department.scala deleted file mode 100644 index c43f781d91..0000000000 --- a/actor-server/actor-models/src/main/scala/im/actor/server/model/Department.scala +++ /dev/null @@ -1,6 +0,0 @@ -package im.actor.server.model - -import com.github.tminglei.slickpg.LTree -import org.joda.time.DateTime - -case class Department(id: Int, name: String, struct: LTree, deletedAt: Option[DateTime] = None) \ No newline at end of file diff --git a/actor-server/actor-models/src/main/scala/im/actor/server/model/FileSourceBlock.scala b/actor-server/actor-models/src/main/scala/im/actor/server/model/FileSourceBlock.scala deleted file mode 100644 index 7cbba0892f..0000000000 --- a/actor-server/actor-models/src/main/scala/im/actor/server/model/FileSourceBlock.scala +++ /dev/null @@ -1,3 +0,0 @@ -package im.actor.server.model - -case class FileSourceBlock(fileId: Int, offset: Int, length: Int) diff --git a/actor-server/actor-models/src/main/scala/im/actor/server/model/Manager.scala b/actor-server/actor-models/src/main/scala/im/actor/server/model/Manager.scala deleted file mode 100644 index c9183a8210..0000000000 --- a/actor-server/actor-models/src/main/scala/im/actor/server/model/Manager.scala +++ /dev/null @@ -1,4 +0,0 @@ -package im.actor.server.model - -@SerialVersionUID(1L) -case class Manager(id: Int, name: String, lastName: String, domain: String, authToken: String, email: String) \ No newline at end of file diff --git a/actor-server/actor-models/src/main/scala/im/actor/server/model/MessageState.scala b/actor-server/actor-models/src/main/scala/im/actor/server/model/MessageState.scala deleted file mode 100644 index 5ff66a34ed..0000000000 --- a/actor-server/actor-models/src/main/scala/im/actor/server/model/MessageState.scala +++ /dev/null @@ -1,28 +0,0 @@ -package im.actor.server.model - -sealed trait MessageState { - def toInt: Int -} - -object MessageState { - @SerialVersionUID(1L) - case object Sent extends MessageState { - def toInt = 1 - } - - @SerialVersionUID(1L) - case object Received extends MessageState { - def toInt = 2 - } - - @SerialVersionUID(1L) - case object Read extends MessageState { - def toInt = 3 - } - - def fromInt(i: Int): MessageState = i match { - case 1 ⇒ Sent - case 2 ⇒ Received - case 3 ⇒ Read - } -} diff --git a/actor-server/actor-models/src/main/scala/im/actor/server/model/UserDepartment.scala b/actor-server/actor-models/src/main/scala/im/actor/server/model/UserDepartment.scala deleted file mode 100644 index 25c3e3b07b..0000000000 --- a/actor-server/actor-models/src/main/scala/im/actor/server/model/UserDepartment.scala +++ /dev/null @@ -1,3 +0,0 @@ -package im.actor.server.model - -case class UserDepartment(userId: Int, departmentId: Int) \ No newline at end of file diff --git a/actor-server/actor-models/src/main/scala/im/actor/server/model/UserPublicKey.scala b/actor-server/actor-models/src/main/scala/im/actor/server/model/UserPublicKey.scala deleted file mode 100644 index e19991194a..0000000000 --- a/actor-server/actor-models/src/main/scala/im/actor/server/model/UserPublicKey.scala +++ /dev/null @@ -1,8 +0,0 @@ -package im.actor.server.model - -@SerialVersionUID(1L) -case class UserPublicKey( - userId: Int, - hash: Long, - data: Array[Byte] -) diff --git a/actor-server/actor-models/src/main/scala/im/actor/server/model/voximplant/VoxUser.scala b/actor-server/actor-models/src/main/scala/im/actor/server/model/voximplant/VoxUser.scala deleted file mode 100644 index 6a40703b1b..0000000000 --- a/actor-server/actor-models/src/main/scala/im/actor/server/model/voximplant/VoxUser.scala +++ /dev/null @@ -1,3 +0,0 @@ -package im.actor.server.model.voximplant - -case class VoxUser(userId: Int, voxUserId: Long, userName: String, displayName: String, salt: String) \ No newline at end of file diff --git a/actor-server/actor-persist/src/main/resources/sql/migration/V20160902182358__SchemaCleanup.sql b/actor-server/actor-persist/src/main/resources/sql/migration/V20160902182358__SchemaCleanup.sql new file mode 100644 index 0000000000..d744afdbc4 --- /dev/null +++ b/actor-server/actor-persist/src/main/resources/sql/migration/V20160902182358__SchemaCleanup.sql @@ -0,0 +1,14 @@ +drop table departments; +drop table llectro_devices; +drop table llectro_interests; +drop table llectro_users; +drop table llectro_users_interests; +drop table managers; +drop table vox_users; +drop table user_department; +drop table webrtc_calls; +drop table auth_sms_codes_obsolete; +drop table file_datas; +drop table file_blocks; +drop table max_dates; +drop table plain_mails; diff --git a/actor-server/actor-persist/src/main/scala/im/actor/server/persist/AuthSmsCodeObsoleteRepo.scala b/actor-server/actor-persist/src/main/scala/im/actor/server/persist/AuthSmsCodeObsoleteRepo.scala deleted file mode 100644 index 27275d06a4..0000000000 --- a/actor-server/actor-persist/src/main/scala/im/actor/server/persist/AuthSmsCodeObsoleteRepo.scala +++ /dev/null @@ -1,31 +0,0 @@ -package im.actor.server.persist - -import im.actor.server.model.AuthSmsCodeObsolete -import slick.driver.PostgresDriver.api._ - -final class AuthSmsCodeObsoleteTable(tag: Tag) extends Table[AuthSmsCodeObsolete](tag, "auth_sms_codes_obsolete") { - def id = column[Long]("id", O.PrimaryKey) - def phoneNumber = column[Long]("phone_number") - def smsHash = column[String]("sms_hash") - def smsCode = column[String]("sms_code") - def isDeleted = column[Boolean]("is_deleted") - - def * = (id, phoneNumber, smsHash, smsCode, isDeleted) <> (AuthSmsCodeObsolete.tupled, AuthSmsCodeObsolete.unapply) -} - -object AuthSmsCodeObsoleteRepo { - val codes = TableQuery[AuthSmsCodeObsoleteTable] - - def byPhoneNumber(number: Rep[Long]) = - codes.filter(c ⇒ c.phoneNumber === number && c.isDeleted === false) - private val byPhoneNumberC = Compiled(byPhoneNumber _) - - def create(id: Long, phoneNumber: Long, smsHash: String, smsCode: String) = - codes += AuthSmsCodeObsolete(id, phoneNumber, smsHash, smsCode) - - def findByPhoneNumber(number: Long) = - byPhoneNumberC(number).result - - def deleteByPhoneNumber(number: Long) = - byPhoneNumber(number).map(_.isDeleted).update(true) -} diff --git a/actor-server/actor-persist/src/main/scala/im/actor/server/persist/DepartmentRepo.scala b/actor-server/actor-persist/src/main/scala/im/actor/server/persist/DepartmentRepo.scala deleted file mode 100644 index f483aa5dfb..0000000000 --- a/actor-server/actor-persist/src/main/scala/im/actor/server/persist/DepartmentRepo.scala +++ /dev/null @@ -1,43 +0,0 @@ -package im.actor.server.persist - -import com.github.tminglei.slickpg.LTree -import com.github.tototoshi.slick.PostgresJodaSupport._ -import org.joda.time.DateTime - -import im.actor.server._ -import im.actor.server.db.ActorPostgresDriver.api._ - -final class DepartmentTable(tag: Tag) extends Table[model.Department](tag, "departments") { - def id = column[Int]("id", O.PrimaryKey) - def name = column[String]("name") - def struct = column[LTree]("struct") - def deletedAt = column[Option[DateTime]]("deleted_at") - def structUnique = index("department_struct_idx", struct, unique = true) - - def * = (id, name, struct, deletedAt) <> (model.Department.tupled, model.Department.unapply) -} - -object DepartmentRepo { - - val departments = TableQuery[DepartmentTable] - - def create(department: model.Department) = - departments += department - - def find(struct: String) = - departments.filter(_.struct === LTree(struct)).result - - def setName(struct: String, name: String) = - departments.filter(_.struct === LTree(struct)).map(_.name).update(name) - - def setDeletedAt(struct: String) = - departments.filter(_.struct === LTree(struct)).map(_.deletedAt).update(Some(new DateTime)) - - def deptAndChildren(struct: String) = { - departments. - filter { e ⇒ LTree(struct).bind @> e.struct }. - sortBy { _.struct }. - result - } - -} \ No newline at end of file diff --git a/actor-server/actor-persist/src/main/scala/im/actor/server/persist/HistoryMessageRepo.scala b/actor-server/actor-persist/src/main/scala/im/actor/server/persist/HistoryMessageRepo.scala index a7d03e9cd1..d7ed77caed 100644 --- a/actor-server/actor-persist/src/main/scala/im/actor/server/persist/HistoryMessageRepo.scala +++ b/actor-server/actor-persist/src/main/scala/im/actor/server/persist/HistoryMessageRepo.scala @@ -2,7 +2,6 @@ package im.actor.server.persist import com.github.tototoshi.slick.PostgresJodaSupport._ import im.actor.server.model.{ Peer, PeerType, HistoryMessage } -import im.actor.server.persist.dialog.DialogRepo import org.joda.time.DateTime import slick.dbio.Effect.{ Write, Read } import slick.driver.PostgresDriver diff --git a/actor-server/actor-persist/src/main/scala/im/actor/server/persist/ManagerRepo.scala b/actor-server/actor-persist/src/main/scala/im/actor/server/persist/ManagerRepo.scala deleted file mode 100644 index 4cfdd37ef9..0000000000 --- a/actor-server/actor-persist/src/main/scala/im/actor/server/persist/ManagerRepo.scala +++ /dev/null @@ -1,27 +0,0 @@ -package im.actor.server.persist - -import im.actor.server.model.Manager -import slick.driver.PostgresDriver.api._ - -final class ManagerTable(tag: Tag) extends Table[Manager](tag, "managers") { - def id = column[Int]("id", O.PrimaryKey) - def name = column[String]("name") - def lastName = column[String]("last_name") - def domain = column[String]("domain") - def authToken = column[String]("auth_token") - def email = column[String]("email") - def emailUnique = index("manager_email_idx", email, unique = true) //way to keep email unique - - def * = (id, name, lastName, domain, authToken, email) <> (Manager.tupled, Manager.unapply) -} - -object ManagerRepo { - val managers = TableQuery[ManagerTable] - - def create(manager: Manager) = - managers += manager - - def findByEmail(email: String) = - managers.filter(_.email === email).result.headOption - -} \ No newline at end of file diff --git a/actor-server/actor-persist/src/main/scala/im/actor/server/persist/MessageStateColumnType.scala b/actor-server/actor-persist/src/main/scala/im/actor/server/persist/MessageStateColumnType.scala deleted file mode 100644 index a236e291c7..0000000000 --- a/actor-server/actor-persist/src/main/scala/im/actor/server/persist/MessageStateColumnType.scala +++ /dev/null @@ -1,9 +0,0 @@ -package im.actor.server.persist - -import im.actor.server.model.MessageState -import slick.driver.PostgresDriver.api._ - -object MessageStateColumnType { - implicit val messageStateColumnType = - MappedColumnType.base[MessageState, Int](_.toInt, MessageState.fromInt) -} diff --git a/actor-server/actor-persist/src/main/scala/im/actor/server/persist/UserDepartmentRepo.scala b/actor-server/actor-persist/src/main/scala/im/actor/server/persist/UserDepartmentRepo.scala deleted file mode 100644 index a98aee4d86..0000000000 --- a/actor-server/actor-persist/src/main/scala/im/actor/server/persist/UserDepartmentRepo.scala +++ /dev/null @@ -1,23 +0,0 @@ -package im.actor.server.persist - -import im.actor.server.db.ActorPostgresDriver.api._ -import im.actor.server.model.UserDepartment - -final class UserDepartmentTable(tag: Tag) extends Table[UserDepartment](tag, "user_department") { - def userId = column[Int]("user_id", O.PrimaryKey) - def departmentId = column[Int]("department_id", O.PrimaryKey) - - def * = (userId, departmentId) <> (UserDepartment.tupled, UserDepartment.unapply) -} - -object UserDepartmentRepo { - - val userDepartments = TableQuery[UserDepartmentTable] - - def create(userId: Int, departmentId: Int) = - userDepartments += UserDepartment(userId, departmentId) - - def userIdsByDepartmentId(deptId: Int) = - userDepartments.filter { _.departmentId === deptId }.map { _.userId }.result - -} \ No newline at end of file diff --git a/actor-server/actor-persist/src/main/scala/im/actor/server/persist/UserPhoneRepo.scala b/actor-server/actor-persist/src/main/scala/im/actor/server/persist/UserPhoneRepo.scala index 2e3af39029..2dfe7a6d72 100644 --- a/actor-server/actor-persist/src/main/scala/im/actor/server/persist/UserPhoneRepo.scala +++ b/actor-server/actor-persist/src/main/scala/im/actor/server/persist/UserPhoneRepo.scala @@ -18,11 +18,11 @@ final class UserPhoneTable(tag: Tag) extends Table[UserPhone](tag, "user_phones" object UserPhoneRepo { val phones = TableQuery[UserPhoneTable] - val byPhoneNumber = Compiled { number: Rep[Long] ⇒ + private val byPhoneNumber = Compiled { number: Rep[Long] ⇒ phones.filter(_.number === number) } - val phoneExists = Compiled { number: Rep[Long] ⇒ + private val phoneExists = Compiled { number: Rep[Long] ⇒ phones.filter(_.number === number).exists } @@ -37,15 +37,6 @@ object UserPhoneRepo { def findByUserId(userId: Int): FixedSqlStreamingAction[Seq[UserPhone], UserPhone, Read] = phones.filter(_.userId === userId).result - def findByUserIds(userIds: Set[Int]): FixedSqlStreamingAction[Seq[UserPhone], UserPhone, Read] = - phones.filter(_.userId inSet userIds).result - def create(id: Int, userId: Int, accessSalt: String, number: Long, title: String): FixedSqlAction[Int, NoStream, Write] = phones += UserPhone(id, userId, accessSalt, number, title) - - def create(userPhone: UserPhone): FixedSqlAction[Int, NoStream, Write] = - phones += userPhone - - def updateTitle(userId: Int, id: Int, title: String) = - phones.filter(p ⇒ p.userId === userId && p.id === id).map(_.title).update(title) } diff --git a/actor-server/actor-persist/src/main/scala/im/actor/server/persist/UserPublicKeyRepo.scala b/actor-server/actor-persist/src/main/scala/im/actor/server/persist/UserPublicKeyRepo.scala deleted file mode 100644 index 19af43d104..0000000000 --- a/actor-server/actor-persist/src/main/scala/im/actor/server/persist/UserPublicKeyRepo.scala +++ /dev/null @@ -1,51 +0,0 @@ -package im.actor.server.persist - -import com.github.tototoshi.slick.PostgresJodaSupport._ -import im.actor.server.model.UserPublicKey -import org.joda.time.DateTime -import slick.driver.PostgresDriver.api._ - -final class UserPublicKeyTable(tag: Tag) extends Table[UserPublicKey](tag, "public_keys") { - def userId = column[Int]("user_id", O.PrimaryKey) - def hash = column[Long]("hash", O.PrimaryKey) - def data = column[Array[Byte]]("data") - def deletedAt = column[Option[DateTime]]("deleted_at") - - def * = (userId, hash, data) <> (UserPublicKey.tupled, UserPublicKey.unapply) -} - -object UserPublicKeyRepo { - val pkeys = TableQuery[UserPublicKeyTable] - - private def active = - pkeys.filter(_.deletedAt.isEmpty) - - private def activeByUserId(userId: Int) = - active.filter(p ⇒ p.userId === userId && p.deletedAt.isEmpty) - - def create(pk: UserPublicKey) = - pkeys += pk - - def delete(userId: Int, hash: Long) = - pkeys.filter(p ⇒ p.userId === userId && p.hash === hash).map(_.deletedAt).update(Some(new DateTime)) - - def find(userId: Int, hash: Long) = - active.filter(p ⇒ p.userId === userId && p.hash === hash).result - - def findByUserId(userId: Int) = - active.filter(_.userId === userId).result - - def findKeyHashes(userId: Int) = - activeByUserId(userId).map(_.hash).result - - def findByUserHashes(pairs: Set[(Int, Long)]) = { - // TODO: type-based size checking - require(pairs.size > 0) - - active.filter { pk ⇒ - pairs.view.map { - case (userId, hash) ⇒ pk.userId === userId && pk.hash === hash - }.reduceLeft(_ || _) - }.result - } -} diff --git a/actor-server/actor-persist/src/main/scala/im/actor/server/persist/dialog/DialogRepo.scala b/actor-server/actor-persist/src/main/scala/im/actor/server/persist/dialog/DialogRepo.scala index cb2a1d3e31..26622204a6 100644 --- a/actor-server/actor-persist/src/main/scala/im/actor/server/persist/dialog/DialogRepo.scala +++ b/actor-server/actor-persist/src/main/scala/im/actor/server/persist/dialog/DialogRepo.scala @@ -4,13 +4,8 @@ import com.github.tototoshi.slick.PostgresJodaSupport._ import im.actor.server.db.ActorPostgresDriver.api._ import im.actor.server.model._ import org.joda.time.DateTime -import slick.dbio.DBIOAction -import slick.dbio.Effect.Read -import slick.lifted.ColumnOrdered -import slick.profile.FixedSqlStreamingAction import scala.concurrent.ExecutionContext -import scala.util.{ Failure, Success } final class DialogCommonTable(tag: Tag) extends Table[DialogCommon](tag, "dialog_commons") { @@ -111,49 +106,6 @@ final class UserDialogTable(tag: Tag) extends Table[UserDialog](tag, "user_dialo object DialogRepo extends UserDialogOperations with DialogCommonOperations { - def create(dialog: DialogObsolete)(implicit ec: ExecutionContext): DBIO[Int] = { - val dialogId = getDialogId(Some(dialog.userId), dialog.peer) - - val common = DialogCommon( - dialogId = dialogId, - lastMessageDate = dialog.lastMessageDate, - lastReceivedAt = dialog.lastReceivedAt, - lastReadAt = dialog.lastReadAt - ) - - val user = UserDialog( - userId = dialog.userId, - peer = dialog.peer, - ownerLastReceivedAt = dialog.ownerLastReceivedAt, - ownerLastReadAt = dialog.ownerLastReadAt, - createdAt = dialog.createdAt, - shownAt = dialog.shownAt, - isFavourite = dialog.isFavourite, - archivedAt = dialog.archivedAt - ) - - for { - exists ← commonExists(dialogId) - result ← if (exists) { - UserDialogRepo.userDialogs += user - } else { - for { - c ← (DialogCommonRepo.dialogCommon += common) - .asTry - .flatMap { - case Failure(e) ⇒ - commonExists(common.dialogId) flatMap { - case true ⇒ DBIO.successful(1) - case false ⇒ DBIO.failed(e) - } - case Success(res) ⇒ DBIO.successful(res) - } - _ ← UserDialogRepo.userDialogs += user - } yield c - } - } yield result - } - private val dialogs = for { c ← DialogCommonRepo.dialogCommon u ← UserDialogRepo.userDialogs if c.dialogId === repDialogId(u.userId, u.peerId, u.peerType) @@ -163,31 +115,17 @@ object DialogRepo extends UserDialogOperations with DialogCommonOperations { private val byUserC = Compiled(byUserId _) - private val archived = DialogRepo.dialogs.filter(_._2.archivedAt.isDefined) - - private val notArchived = DialogRepo.dialogs.filter(_._2.archivedAt.isEmpty) - - private def archivedByUserId( - userId: Rep[Int], - offset: ConstColumn[Long], - limit: ConstColumn[Long] - ) = archived filter (_._2.userId === userId) drop offset take limit - - private val archivedByUserIdC = Compiled(archivedByUserId _) - - private val archivedExistC = Compiled { (userId: Rep[Int]) ⇒ - archivedByUserId(userId, 0L, 1L).take(1).exists - } - private def byPKSimple(userId: Rep[Int], peerType: Rep[Int], peerId: Rep[Int]) = dialogs.filter({ case (_, u) ⇒ u.userId === userId && u.peerType === peerType && u.peerId === peerId }) private def byUserId(userId: Rep[Int]) = dialogs.filter({ case (_, u) ⇒ u.userId === userId }) + @deprecated("Migrations only", "2016-09-02") def findDialog(userId: Int, peer: Peer)(implicit ec: ExecutionContext): DBIO[Option[DialogObsolete]] = byPKC((userId, peer.typ.value, peer.id)).result.headOption map (_.map { case (c, u) ⇒ DialogObsolete.fromCommonAndUser(c, u) }) + @deprecated("Migrations only", "2016-09-02") def fetchDialogs(userId: Int)(implicit ec: ExecutionContext): DBIO[Seq[DialogObsolete]] = byUserC(userId).result map (_.map { case (c, u) ⇒ DialogObsolete.fromCommonAndUser(c, u) }) -} \ No newline at end of file +} diff --git a/actor-server/actor-persist/src/main/scala/im/actor/server/persist/encryption/EphermalPublicKeyRepo.scala b/actor-server/actor-persist/src/main/scala/im/actor/server/persist/encryption/EphermalPublicKeyRepo.scala index a5b08a2c4a..0fbeae72b5 100644 --- a/actor-server/actor-persist/src/main/scala/im/actor/server/persist/encryption/EphermalPublicKeyRepo.scala +++ b/actor-server/actor-persist/src/main/scala/im/actor/server/persist/encryption/EphermalPublicKeyRepo.scala @@ -44,4 +44,4 @@ object EphermalPublicKeyRepo { def fetch(userId: Int, keyGroupId: Int, keyIds: Set[Long]) = byUserIdKeyGroupC.applied(userId → keyGroupId).filter(_.keyId inSet keyIds).result -} \ No newline at end of file +} diff --git a/actor-server/actor-persist/src/main/scala/im/actor/server/persist/voximplant/VoxUser.scala b/actor-server/actor-persist/src/main/scala/im/actor/server/persist/voximplant/VoxUser.scala deleted file mode 100644 index 2b0e79a9f0..0000000000 --- a/actor-server/actor-persist/src/main/scala/im/actor/server/persist/voximplant/VoxUser.scala +++ /dev/null @@ -1,27 +0,0 @@ -package im.actor.server.persist.voximplant - -import im.actor.server.model.voximplant.{ VoxUser ⇒ VoxUserModel } -import slick.driver.PostgresDriver.api._ - -class VoxUserTable(tag: Tag) extends Table[VoxUserModel](tag, "vox_users") { - def userId = column[Int]("user_id", O.PrimaryKey) - def voxUserId = column[Long]("vox_user_id") - def userName = column[String]("user_name") - def displayName = column[String]("display_name") - def salt = column[String]("salt") - - def * = (userId, voxUserId, userName, displayName, salt) <> (VoxUserModel.tupled, VoxUserModel.unapply) -} - -object VoxUser { - val users = TableQuery[VoxUserTable] - - def create(user: VoxUserModel) = - users += user - - def createOrReplace(user: VoxUserModel) = - users.insertOrUpdate(user) - - def findByUserId(userId: Int) = - users.filter(_.userId === userId).result.headOption -} diff --git a/actor-server/actor-persist/src/main/scala/im/actor/server/persist/webrtc/WebrtcCallRepo.scala b/actor-server/actor-persist/src/main/scala/im/actor/server/persist/webrtc/WebrtcCallRepo.scala deleted file mode 100644 index fd8cea2603..0000000000 --- a/actor-server/actor-persist/src/main/scala/im/actor/server/persist/webrtc/WebrtcCallRepo.scala +++ /dev/null @@ -1,28 +0,0 @@ -package im.actor.server.persist.webrtc - -import im.actor.server.db.ActorPostgresDriver.api._ -import im.actor.server.webrtc.WebrtcCall - -final class WebrtcCallTable(tag: Tag) extends Table[WebrtcCall](tag, "webrtc_calls") { - def id = column[Long]("id", O.PrimaryKey) - - def initiatorUserId = column[Int]("initiator_user_id") - - def receiverUserId = column[Int]("receiver_user_id") - - def * = (id, initiatorUserId, receiverUserId) <> ((WebrtcCall.apply _).tupled, WebrtcCall.unapply) -} - -object WebrtcCallRepo { - val webrtcCalls = TableQuery[WebrtcCallTable] - - val byPKC = Compiled { id: Rep[Long] ⇒ - webrtcCalls filter (_.id === id) - } - - def create(call: WebrtcCall) = webrtcCalls += call - - def find(id: Long) = byPKC(id).result.headOption - - def delete(id: Long) = byPKC(id).delete -} \ No newline at end of file From 1fab992a735377be86396c2abea7e81e5e1d1c06 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Thu, 8 Sep 2016 20:02:31 +0300 Subject: [PATCH 335/414] feat(android): attach menu appear animation --- .../conversation/ChatFragment.java | 2 +- .../conversation/attach/AttachFragment.java | 324 ++++++++++++------ .../attach/FastAttachAdapter.java | 15 +- .../mentions/AutocompleteFragment.java | 34 -- .../java/im/actor/sdk/util/ViewUtils.java | 64 ++++ .../main/res-material/layout/share_menu.xml | 10 +- .../java/im/actor/core/AndroidMessenger.java | 9 +- 7 files changed, 310 insertions(+), 148 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatFragment.java index b26975ec76..a0c1ebb1c5 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatFragment.java @@ -344,7 +344,7 @@ public void onAttachPressed() { AbsAttachFragment attachFragment = findShareFragment(); if (attachFragment != null) { - quoteContainer.post(() -> attachFragment.show()); + quoteContainer.postDelayed(() -> attachFragment.show(), 200); } } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/attach/AttachFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/attach/AttachFragment.java index 75694e9829..f6142b8b5a 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/attach/AttachFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/attach/AttachFragment.java @@ -15,7 +15,6 @@ import android.support.annotation.Nullable; import android.support.v4.content.ContextCompat; import android.support.v7.widget.GridLayoutManager; -import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.Base64; import android.view.Gravity; @@ -23,6 +22,7 @@ import android.view.View; import android.view.ViewAnimationUtils; import android.view.ViewGroup; +import android.view.animation.TranslateAnimation; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.LinearLayout; @@ -42,6 +42,7 @@ import im.actor.sdk.controllers.tools.MediaPickerFragment; import im.actor.sdk.util.SDKFeatures; import im.actor.sdk.util.Screen; +import im.actor.sdk.view.MaterialInterpolator; import im.actor.sdk.view.ShareMenuButtonFactory; import im.actor.sdk.view.adapters.HeaderViewRecyclerAdapter; @@ -53,13 +54,22 @@ public class AttachFragment extends AbsAttachFragment implements MediaPickerCall public static final int SPAN_COUNT = 4; private FrameLayout root; - private View container; + private View shareButtons; private FastAttachAdapter fastAttachAdapter; private ImageView menuIconToChange; private TextView menuTitleToChange; + private ImageView menuIconToChangeClone; + private TextView menuTitleToChangeClone; private boolean isLoaded = false; private RecyclerView fastShare; + private View bottomBackground; + private boolean isFastShareFullScreen; + private GridLayoutManager layoutManager; + private int shareIconSize; + private View hideClone; + private int fastShareWidth; + private int spanCount; public AttachFragment(Peer peer) { super(peer); @@ -68,6 +78,21 @@ public AttachFragment(Peer peer) { public AttachFragment() { } + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + hide(); + if (layoutManager != null) { + layoutManager = getGridLayoutManager(); + } + root.removeAllViews(); + isLoaded = false; + } + + protected GridLayoutManager getLayoutManager() { + return layoutManager; + } + @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup fcontainer, @Nullable Bundle savedInstanceState) { @@ -78,11 +103,24 @@ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup fcontainer .commitNow(); } - root = new FrameLayout(getContext()); + root = new FrameLayout(getContext()) { + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + if (h != oldh && shareButtons != null) { + shareButtons.getLayoutParams().height = root.getHeight() - Screen.dp(135); + shareButtons.requestLayout(); + } + } + }; root.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); + root.setBackgroundColor(getActivity().getResources().getColor(R.color.dialog_overlay)); + root.setVisibility(View.INVISIBLE); isLoaded = false; +// messenger().getGalleryScannerActor().send(new GalleryScannerActor.Show()); +// messenger().getGalleryScannerActor().send(new GalleryScannerActor.Hide()); return root; } @@ -93,37 +131,57 @@ private void prepareView() { } isLoaded = true; - container = getLayoutInflater(null).inflate(R.layout.share_menu, root, false); + shareButtons = getLayoutInflater(null).inflate(R.layout.share_menu, root, false); fastShare = new RecyclerView(getActivity()); - fastShare.setVisibility(View.INVISIBLE); + fastShare.setOverScrollMode(View.OVER_SCROLL_NEVER); - container.findViewById(R.id.menu_bg).setBackgroundColor(style.getMainBackgroundColor()); - container.findViewById(R.id.cancelField).setOnClickListener(view -> hide()); + shareButtons.findViewById(R.id.menu_bg).setBackgroundColor(style.getMainBackgroundColor()); + shareButtons.findViewById(R.id.cancelField).setOnClickListener(view -> hide()); + + // + // Setup appearing hide button + // + isFastShareFullScreen = false; + fastShare.addOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrolled(RecyclerView recyclerView, int dx, int dy) { + boolean visible = layoutManager.findFirstVisibleItemPosition() == 0; + if (isFastShareFullScreen == visible) { + isFastShareFullScreen = !visible; + if (visible) { + hideView(hideClone); + } else { + showView(hideClone); + } + } + } + }); // // Building Menu Fields // ArrayList menuFields = new ArrayList<>(onCreateFields()); // Adding Additional Hide for better UI + ShareMenuField shareMenuFieldHide = new ShareMenuField(R.id.share_hide, R.drawable.attach_hide2, style.getAccentColor(), ""); if (menuFields.size() % 2 != 0) { - menuFields.add(new ShareMenuField(R.id.share_hide, R.drawable.attach_hide2, style.getBackyardBackgroundColor(), "")); + menuFields.add(shareMenuFieldHide); } // // Building Layout // - FrameLayout row = (FrameLayout) container.findViewById(R.id.share_row_one); + FrameLayout row = (FrameLayout) shareButtons.findViewById(R.id.share_row_one); boolean first = true; int menuItemSize = Screen.dp(80); int screenWidth = (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT ? Screen.getWidth() - : Screen.getHeight()); + : Screen.getWidth()); int distance = screenWidth / (menuFields.size() / 2 + menuFields.size() % 2); int initialMargin = distance / 2 - menuItemSize / 2; int marginFromStart = initialMargin; int secondRowTopMargin = Screen.dp(96); - int shareIconSize = Screen.dp(60); + shareIconSize = Screen.dp(60); View.OnClickListener defaultSendOcl = null; Configuration config = getResources().getConfiguration(); @@ -135,43 +193,15 @@ private void prepareView() { for (int i = 0; i < menuFields.size(); i++) { ShareMenuField f = menuFields.get(i); - LinearLayout shareItem = new LinearLayout(getActivity()); - shareItem.setOrientation(LinearLayout.VERTICAL); - shareItem.setGravity(Gravity.CENTER_HORIZONTAL); - - TextView title = new TextView(getActivity()); - title.setGravity(Gravity.CENTER); - title.setTextColor(ActorSDK.sharedActor().style.getTextSecondaryColor()); - title.setText(f.getTitle()); - title.setTextSize(14); - - ImageView icon = new ImageView(getActivity()); - icon.setClickable(true); - if (f.getSelector() != 0) { - icon.setBackgroundResource(f.getSelector()); - } else { - icon.setBackgroundDrawable(ShareMenuButtonFactory.get(f.getColor(), getActivity())); - icon.setScaleType(ImageView.ScaleType.CENTER_INSIDE); - icon.setImageResource(f.getIcon()); - } - - shareItem.addView(icon, shareIconSize, shareIconSize); - shareItem.addView(title); - - View.OnClickListener l = v -> { - hide(); - onItemClicked(v.getId()); - }; - icon.setId(f.getId()); - icon.setOnClickListener(l); + View shareItem = instantiateShareMenuItem(f); FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(menuItemSize, menuItemSize); params.setMargins(isRtl ? initialMargin : marginFromStart, first ? 0 : secondRowTopMargin, isRtl ? marginFromStart : initialMargin, 0); if (i == menuFields.size() - 1) { - menuIconToChange = icon; - menuTitleToChange = title; - defaultSendOcl = l; + menuIconToChange = (ImageView) shareItem.getTag(R.id.icon); + menuTitleToChange = (TextView) shareItem.getTag(R.id.title); + defaultSendOcl = (View.OnClickListener) shareItem.getTag(R.id.list); params.setMargins(isRtl ? 0 : marginFromStart, first ? 0 : secondRowTopMargin, isRtl ? marginFromStart : 0, 0); @@ -183,6 +213,12 @@ private void prepareView() { first = !first; } + hideClone = instantiateShareMenuItem(shareMenuFieldHide); + hideClone.setVisibility(View.INVISIBLE); + menuTitleToChangeClone = (TextView) hideClone.getTag(R.id.title); + menuIconToChangeClone = (ImageView) hideClone.getTag(R.id.icon); + menuTitleToChangeClone.setVisibility(View.GONE); + menuIconToChange.setTag(R.id.icon, menuIconToChange.getDrawable()); menuIconToChange.setTag(R.id.background, menuIconToChange.getBackground()); menuTitleToChange.setTag(menuTitleToChange.getText().toString()); @@ -197,17 +233,18 @@ private void prepareView() { hide(); }; -// RecyclerView fastShare = (RecyclerView) container.findViewById(R.id.fast_share); - fastAttachAdapter = new FastAttachAdapter(getActivity()); +// RecyclerView fastShare = (RecyclerView) shareButtons.findViewById(R.id.fast_share); + fastAttachAdapter = new FastAttachAdapter(getActivity(), () -> fastShareWidth + 1); + HeaderViewRecyclerAdapter adapter = new HeaderViewRecyclerAdapter(fastAttachAdapter); - adapter.addHeaderView(container); - GridLayoutManager layoutManager = new GridLayoutManager(getActivity(), SPAN_COUNT); + adapter.addHeaderView(shareButtons); + layoutManager = getGridLayoutManager(); fastShare.setAdapter(adapter); fastShare.setLayoutManager(layoutManager); layoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { @Override public int getSpanSize(int position) { - return position == 0 ? 4 : 1; + return position == 0 ? spanCount : 1; } }); StateListDrawable background = ShareMenuButtonFactory.get(style.getMainColor(), getActivity()); @@ -221,6 +258,13 @@ public int getSpanSize(int position) { menuTitleToChange.setText(getString(R.string.chat_doc_send) + "(" + val.size() + ")"); menuIconToChange.setOnClickListener(shareSendOcl); menuIconToChange.setPadding(Screen.dp(10), 0, Screen.dp(5), 0); + + + menuIconToChangeClone.setBackgroundDrawable(background); + menuIconToChangeClone.setImageResource(R.drawable.conv_send); + menuIconToChangeClone.setColorFilter(0xffffffff, PorterDuff.Mode.SRC_IN); + menuIconToChangeClone.setOnClickListener(shareSendOcl); + menuIconToChangeClone.setPadding(Screen.dp(10), 0, Screen.dp(5), 0); } else { menuIconToChange.setBackgroundDrawable((Drawable) menuIconToChange.getTag(R.id.background)); @@ -229,22 +273,78 @@ public int getSpanSize(int position) { menuIconToChange.setOnClickListener(finalDefaultSendOcl); menuTitleToChange.setText((String) menuTitleToChange.getTag()); menuIconToChange.setPadding(0, 0, 0, 0); + + menuIconToChangeClone.setBackgroundDrawable((Drawable) menuIconToChange.getTag(R.id.background)); + menuIconToChangeClone.setImageDrawable((Drawable) menuIconToChange.getTag(R.id.icon)); + menuIconToChangeClone.setColorFilter(null); + menuIconToChangeClone.setOnClickListener(finalDefaultSendOcl); + menuIconToChangeClone.setPadding(0, 0, 0, 0); } }); - root.post(new Runnable() { - @Override - public void run() { - container.getLayoutParams().height = root.getHeight() - Screen.dp(135); - container.requestLayout(); - } - }); + + shareButtons.getLayoutParams().height = root.getHeight() - Screen.dp(135); + shareButtons.requestLayout(); + + + bottomBackground = new View(getContext()); + FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, Screen.dp(135), Gravity.BOTTOM); + bottomBackground.setBackgroundColor(ActorSDK.sharedActor().style.getMainBackgroundColor()); + root.addView(bottomBackground, params); root.addView(fastShare); + FrameLayout.LayoutParams params2 = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.BOTTOM | Gravity.RIGHT); + params2.setMargins(0, 0, Screen.dp(20), Screen.dp(20)); + root.addView(hideClone, params2); + } + + @NonNull + private GridLayoutManager getGridLayoutManager() { + spanCount = Screen.getWidth() / Screen.dp(88); + fastShareWidth = Screen.getWidth() / spanCount; + return new GridLayoutManager(getActivity(), spanCount); + } + + private View instantiateShareMenuItem(ShareMenuField f) { + LinearLayout shareItem = new LinearLayout(getActivity()); + shareItem.setOrientation(LinearLayout.VERTICAL); + shareItem.setGravity(Gravity.CENTER_HORIZONTAL); + + TextView title = new TextView(getActivity()); + title.setGravity(Gravity.CENTER); + title.setTextColor(ActorSDK.sharedActor().style.getTextSecondaryColor()); + title.setText(f.getTitle()); + title.setTextSize(14); + + ImageView icon = new ImageView(getActivity()); + icon.setClickable(true); + if (f.getSelector() != 0) { + icon.setBackgroundResource(f.getSelector()); + } else { + icon.setBackgroundDrawable(ShareMenuButtonFactory.get(f.getColor(), getActivity())); + icon.setScaleType(ImageView.ScaleType.CENTER_INSIDE); + icon.setImageResource(f.getIcon()); + } + + shareItem.addView(icon, shareIconSize, shareIconSize); + shareItem.addView(title); + + View.OnClickListener l = v -> { + hide(); + onItemClicked(v.getId()); + }; + icon.setId(f.getId()); + icon.setOnClickListener(l); + + shareItem.setTag(R.id.title, title); + shareItem.setTag(R.id.icon, icon); + shareItem.setTag(R.id.list, l); + + return shareItem; } @Override public void show() { prepareView(); - if (fastShare.getVisibility() == View.INVISIBLE) { + if (root.getVisibility() == View.INVISIBLE) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { Activity activity = getActivity(); if (activity == null) { @@ -259,58 +359,78 @@ public void show() { } onShown(); messenger().getGalleryScannerActor().send(new GalleryScannerActor.Show()); - showView(fastShare); -// if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { -// View internal = container.findViewById(R.id.menu_bg); -// int cx = internal.getWidth() - Screen.dp(56 + 56); -// int cy = internal.getHeight() - Screen.dp(56 / 2); -// float finalRadius = (float) Math.hypot(cx, cy); -// Animator anim = ViewAnimationUtils.createCircularReveal(internal, cx, cy, 0, finalRadius); -// anim.setDuration(200); -// anim.start(); -// internal.setAlpha(1); -// } + showView(root); + TranslateAnimation animation = new TranslateAnimation(0, 0, root.getHeight(), 0); + animation.setInterpolator(MaterialInterpolator.getInstance()); + animation.setDuration(200); +// fastShare.startAnimation(animation); +// bottomBackground.startAnimation(animation); + shareButtons.post(new Runnable() { + @Override + public void run() { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { + View internal = fastShare; + int cx = internal.getWidth() - Screen.dp(56 + 56); + int cy = internal.getHeight() - Screen.dp(56 / 2); + float finalRadius = (float) Math.hypot(cx, cy); + Animator anim = ViewAnimationUtils.createCircularReveal(internal, cx, cy, 0, finalRadius); + anim.setDuration(200); + anim.start(); + internal.setAlpha(1); + } + } + }); + } } @Override public void hide() { - if (fastShare != null && fastShare.getVisibility() == View.VISIBLE) { + if (root != null && root.getVisibility() == View.VISIBLE) { onHidden(); fastAttachAdapter.clearSelected(); messenger().getGalleryScannerActor().send(new GalleryScannerActor.Hide()); - hideView(fastShare); -// if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { -// View internal = container.findViewById(R.id.menu_bg); -// int cx = internal.getWidth() - Screen.dp(56 + 56); -// int cy = internal.getHeight() - Screen.dp(56 / 2); -// float finalRadius = (float) Math.hypot(cx, cy); -// Animator anim = ViewAnimationUtils.createCircularReveal(internal, cx, cy, finalRadius, 0); -// anim.addListener(new Animator.AnimatorListener() { -// @Override -// public void onAnimationStart(Animator animator) { -// internal.setAlpha(1); -// } -// -// @Override -// public void onAnimationEnd(Animator animator) { -// internal.setAlpha(0); -// } -// -// @Override -// public void onAnimationCancel(Animator animator) { -// -// } -// -// @Override -// public void onAnimationRepeat(Animator animator) { -// -// } -// }); -// -// anim.setDuration(200); -// anim.start(); -// } + fastShare.scrollToPosition(0); + hideView(root); + + + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP && !isFastShareFullScreen) { + View internal = fastShare; + int cx = internal.getWidth() - Screen.dp(56 + 56); + int cy = internal.getHeight() - Screen.dp(56 / 2); + float finalRadius = (float) Math.hypot(cx, cy); + Animator anim = ViewAnimationUtils.createCircularReveal(internal, cx, cy, finalRadius, 0); + anim.addListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animator) { + internal.setAlpha(1); + } + + @Override + public void onAnimationEnd(Animator animator) { + internal.setAlpha(0); + } + + @Override + public void onAnimationCancel(Animator animator) { + + } + + @Override + public void onAnimationRepeat(Animator animator) { + + } + }); + + anim.setDuration(200); + anim.start(); + } else { + TranslateAnimation animation = new TranslateAnimation(0, 0, 0, root.getHeight()); + animation.setInterpolator(MaterialInterpolator.getInstance()); + animation.setDuration(250); + fastShare.startAnimation(animation); + bottomBackground.startAnimation(animation); + } } } @@ -399,7 +519,7 @@ public void onLocationPicked(double longitude, double latitude, String street, S @Override public boolean onBackPressed() { - if (container != null && container.getVisibility() == View.VISIBLE) { + if (root != null && root.getVisibility() == View.VISIBLE) { hide(); return true; } @@ -429,7 +549,7 @@ public void onDestroyView() { fastAttachAdapter.release(); fastAttachAdapter = null; } - container = null; + shareButtons = null; fastShare = null; root = null; menuIconToChange = null; diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/attach/FastAttachAdapter.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/attach/FastAttachAdapter.java index 41b8db5e39..562956591b 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/attach/FastAttachAdapter.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/attach/FastAttachAdapter.java @@ -38,9 +38,12 @@ public class FastAttachAdapter extends RecyclerView.Adapter> selectedVM; private ActorBinder binder; + private WidthGetter widthGetter; - public FastAttachAdapter(Context context) { + public FastAttachAdapter(Context context, WidthGetter widthGetter) { + this.widthGetter = widthGetter; this.context = context; +// setHasStableIds(true); binder = new ActorBinder(); binder.bind(messenger().getGalleryVM().getGalleryMediaPath(), (val, valueModel) -> { imagesPath.clear(); @@ -50,19 +53,23 @@ public FastAttachAdapter(Context context) { selectedVM = new ValueModel<>("fast_share.selected", new HashSet<>()); } + protected View inflate(int id, ViewGroup viewGroup) { return LayoutInflater .from(context) .inflate(id, viewGroup, false); } + public void release() { binder.unbindAll(); } @Override public FastShareVH onCreateViewHolder(ViewGroup parent, int viewType) { - return new FastShareVH(inflate(R.layout.share_menu_fast_share, parent)); + View itemView = inflate(R.layout.share_menu_fast_share, parent); + itemView.setLayoutParams(new ViewGroup.LayoutParams(widthGetter.get(), widthGetter.get())); + return new FastShareVH(itemView); } @Override @@ -128,4 +135,8 @@ public void clearSelected() { public ValueModel> getSelectedVM() { return selectedVM; } + + public interface WidthGetter { + int get(); + } } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/mentions/AutocompleteFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/mentions/AutocompleteFragment.java index 7f3d850876..949098a07c 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/mentions/AutocompleteFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/mentions/AutocompleteFragment.java @@ -160,40 +160,6 @@ private void expandMentions(final BottomSheetListView list, final int oldRowsCou } - private static class ExpandAnimation extends Animation { - - private final View v; - private final int targetHeight; - private final int initialHeight; - private int currentHeight; - - public ExpandAnimation(View v, int targetHeight, int initialHeight) { - this.v = v; - this.targetHeight = targetHeight; - this.initialHeight = initialHeight; - this.currentHeight = initialHeight; - } - - @Override - protected void applyTransformation(float interpolatedTime, Transformation t) { - if (targetHeight > initialHeight) { - currentHeight = - (int) ((targetHeight * interpolatedTime) - initialHeight * interpolatedTime + initialHeight); - } else { - currentHeight = - (int) (initialHeight - (initialHeight * interpolatedTime) - targetHeight * (1f - interpolatedTime) + targetHeight); - } - - v.getLayoutParams().height = currentHeight; - v.requestLayout(); - } - - @Override - public boolean willChangeBounds() { - return true; - } - } - public void setUnderlyingView(View underlyingView) { this.underlyingView = underlyingView; } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/util/ViewUtils.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/util/ViewUtils.java index f7bd2f200f..e556112a86 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/util/ViewUtils.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/util/ViewUtils.java @@ -7,6 +7,7 @@ import android.view.animation.AlphaAnimation; import android.view.animation.Animation; import android.view.animation.ScaleAnimation; +import android.view.animation.Transformation; import im.actor.sdk.view.MaterialInterpolator; @@ -285,5 +286,68 @@ public static void wave(final View layer, float scale, int duration, float stepO layer.startAnimation(scaleAnimation); } + public static void expandView(View v, int targetHeight, int initialHeight, After after) { + Animation a = new ExpandAnimation(v, targetHeight, initialHeight); + + a.setDuration((targetHeight > initialHeight ? targetHeight : initialHeight / Screen.dp(1))); + a.setInterpolator(MaterialInterpolator.getInstance()); + v.clearAnimation(); + a.setAnimationListener(new Animation.AnimationListener() { + @Override + public void onAnimationStart(Animation animation) { + + } + + @Override + public void onAnimationEnd(Animation animation) { + after.doAfter(); + } + + @Override + public void onAnimationRepeat(Animation animation) { + + } + }); + v.startAnimation(a); + + } + + private static class ExpandAnimation extends Animation { + + private final View v; + private final int targetHeight; + private final int initialHeight; + private int currentHeight; + + public ExpandAnimation(View v, int targetHeight, int initialHeight) { + this.v = v; + this.targetHeight = targetHeight; + this.initialHeight = initialHeight; + this.currentHeight = initialHeight; + } + + @Override + protected void applyTransformation(float interpolatedTime, Transformation t) { + if (targetHeight > initialHeight) { + currentHeight = + (int) ((targetHeight * interpolatedTime) - initialHeight * interpolatedTime + initialHeight); + } else { + currentHeight = + (int) (initialHeight - (initialHeight * interpolatedTime) - targetHeight * (1f - interpolatedTime) + targetHeight); + } + + v.getLayoutParams().height = currentHeight; + v.requestLayout(); + } + + @Override + public boolean willChangeBounds() { + return true; + } + } + + public interface After { + void doAfter(); + } } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res-material/layout/share_menu.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res-material/layout/share_menu.xml index b3bde0fb5b..6015f16674 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res-material/layout/share_menu.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res-material/layout/share_menu.xml @@ -7,21 +7,15 @@ android:id="@+id/cancelField" android:layout_width="match_parent" android:layout_height="match_parent" - android:background="@color/dialog_overlay" android:clickable="true" /> - - - - - - getDocsDisplayList(final Peer peer) { public GalleryVM getGalleryVM() { if (galleryVM == null) { galleryVM = new GalleryVM(); + checkGalleryScannerActor(); + } + return galleryVM; + } + + protected void checkGalleryScannerActor() { + if (galleryScannerActor == null) { galleryScannerActor = ActorSystem.system().actorOf(Props.create(new ActorCreator() { @Override public Actor create() { @@ -520,10 +527,10 @@ public Actor create() { } }), "actor/gallery_scanner"); } - return galleryVM; } public ActorRef getGalleryScannerActor() { + checkGalleryScannerActor(); return galleryScannerActor; } From 16b69975983576c8efc0313257908b47a05fcc9c Mon Sep 17 00:00:00 2001 From: kor_ka Date: Mon, 12 Sep 2016 12:36:04 +0300 Subject: [PATCH 336/414] fix(android): commandsAdapter empty on create --- .../controllers/conversation/mentions/AutocompleteFragment.java | 1 - .../sdk/controllers/conversation/mentions/CommandsAdapter.java | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/mentions/AutocompleteFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/mentions/AutocompleteFragment.java index 949098a07c..bf19a9a186 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/mentions/AutocompleteFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/mentions/AutocompleteFragment.java @@ -148,7 +148,6 @@ private void expandMentions(final BottomSheetListView list, final int oldRowsCou return; } - list.setMinHeight(newRowsCount == 0 ? 0 : newRowsCount == 1 ? Screen.dp(48) + 1 : newRowsCount == 2 ? Screen.dp(96) + 2 : Screen.dp(122)); list.setVisibility(View.VISIBLE); // Animation a = new ExpandAnimation(list, targetHeight, initialHeight); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/mentions/CommandsAdapter.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/mentions/CommandsAdapter.java index db5b0a565d..bf2ade3044 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/mentions/CommandsAdapter.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/mentions/CommandsAdapter.java @@ -34,7 +34,7 @@ public CommandsAdapter(int uid, Context context) { botUser = users().get(uid); highlightColor = context.getResources().getColor(R.color.primary); commands = users().get(uid).getBotCommands().get(); - commandsToShow = new ArrayList<>(commands); + commandsToShow = new ArrayList<>(); this.uid = uid; } From e7e2f44193c1996ed22bb8af3b6c62222ef74467 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Tue, 13 Sep 2016 15:42:29 +0300 Subject: [PATCH 337/414] fix(android): prevent conversation cyclic update after messages delete --- .../conversation/messages/MessagesFragment.java | 6 ++++-- .../src/main/java/im/actor/core/Messenger.java | 2 +- .../core/modules/conductor/DisplayLists.java | 2 +- .../modules/messaging/router/RouterActor.java | 15 ++++++++++++--- .../im/actor/core/viewmodel/ConversationVM.java | 2 +- 5 files changed, 19 insertions(+), 8 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/MessagesFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/MessagesFragment.java index ddf91688ba..df180a6f63 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/MessagesFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/MessagesFragment.java @@ -44,6 +44,7 @@ public abstract class MessagesFragment extends DisplayListFragment list = getDisplayList(); if (firstUnread == -1) { - firstUnread = conversationVM.getLastMessageDate(); + firstUnread = conversationVM.getLastReadMessageDate(); } // Do not scroll to unread twice @@ -199,7 +200,8 @@ private void recalculateUnreadMessageIfNeeded() { } // refresh list if top message is too old - if (getDisplayList().getItem(0).getSortDate() < firstUnread) { + if (getDisplayList().getItem(0).getSortDate() < firstUnread && !reloaded) { + reloaded = true; getDisplayList().initCenter(firstUnread, true); return; } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java index 59137e85fd..75971c46af 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java @@ -1120,7 +1120,7 @@ public String loadDraft(Peer peer) { @ObjectiveCName("loadLastMessageDate:") @Deprecated public long loadLastMessageDate(Peer peer) { - return getConversationVM(peer).getLastMessageDate(); + return getConversationVM(peer).getLastReadMessageDate(); } /** diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/conductor/DisplayLists.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/conductor/DisplayLists.java index e485d92008..9a5babc444 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/conductor/DisplayLists.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/conductor/DisplayLists.java @@ -95,7 +95,7 @@ public PlatformDisplayList buildChatList(final Peer peer, boolean isSha PlatformDisplayList res = Storage.createDisplayList(context().getMessagesModule().getConversationEngine(peer), isShared, Message.ENTITY_NAME); - long lastRead = context().getMessagesModule().getConversationVM(peer).getLastMessageDate(); + long lastRead = context().getMessagesModule().getConversationVM(peer).getLastReadMessageDate(); if (lastRead != 0) { res.initCenter(lastRead); diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java index 88f3ee8928..b711a0dfe2 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java @@ -517,8 +517,16 @@ private Promise onMessageDeleted(Peer peer, List rids) { Message head = conversation(peer).getHeadValue(); - if (head != null && head.getMessageState() == MessageState.PENDING) { - head = null; + if (head != null) { + ConversationState state = conversationStates.getValue(peer.getUnuqueId()); + state = state + .changeInReadDate(head.getSortDate()) + .changeOutSendDate(head.getSortDate()); + conversationStates.addOrUpdateItem(state); + + if (head.getMessageState() == MessageState.PENDING) { + head = null; + } } return getDialogsRouter().onMessageDeleted(peer, head); @@ -736,8 +744,9 @@ private void updateChatState(Peer peer) { ConversationState state = conversationStates.getValue(peer.getUnuqueId()); if (state.isEmpty() != isEmpty) { state = state.changeIsEmpty(isEmpty); - conversationStates.addOrUpdateItem(state); } + + conversationStates.addOrUpdateItem(state); } // diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/ConversationVM.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/ConversationVM.java index 8fb7029df2..1c91885870 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/ConversationVM.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/ConversationVM.java @@ -58,7 +58,7 @@ public ValueModel getReceiveDate() { return receiveDate; } - public long getLastMessageDate() { + public long getLastReadMessageDate() { return Math.max(ownReadDate.get(), ownSendDate.get()); } From 8a678d753215ad8175ca6e4da1246e8e2c2f1ee4 Mon Sep 17 00:00:00 2001 From: rockjam Date: Mon, 12 Sep 2016 21:10:45 +0300 Subject: [PATCH 338/414] chore(server): dependency updates --- .../server/bot/http/BotsHttpHandler.scala | 2 +- .../im/actor/api/rpc/DBIOResultRpc.scala | 1 + .../im/actor/api/rpc/MaybeAuthorized.scala | 4 +--- .../encryption/EncryptionApiConverters.scala | 2 +- .../encryption/EncryptionExtension.scala | 2 +- .../actor/server/push/actor/ActorPush.scala | 6 +++--- .../server/api/http/json/JsonEncoders.scala | 2 +- .../server/api/rpc/service/auth/Helpers.scala | 10 ++++++---- .../im/actor/concurrent/FutureResult.scala | 2 +- .../im/actor/util/misc/StringUtils.scala | 4 ++-- actor-server/docker.sh | 2 +- actor-server/project/Build.scala | 2 +- actor-server/project/Dependencies.scala | 20 +++++++++---------- 13 files changed, 29 insertions(+), 30 deletions(-) diff --git a/actor-server/actor-bots/src/main/scala/im/actor/server/bot/http/BotsHttpHandler.scala b/actor-server/actor-bots/src/main/scala/im/actor/server/bot/http/BotsHttpHandler.scala index 8280d58e55..28be1f9590 100644 --- a/actor-server/actor-bots/src/main/scala/im/actor/server/bot/http/BotsHttpHandler.scala +++ b/actor-server/actor-bots/src/main/scala/im/actor/server/bot/http/BotsHttpHandler.scala @@ -12,7 +12,7 @@ import akka.stream.{ ActorMaterializer, Materializer } import akka.stream.scaladsl.Flow import akka.util.ByteString import cats.data.OptionT -import cats.std.future._ +import cats.instances.future._ import de.heikoseeberger.akkahttpplayjson.PlayJsonSupport import im.actor.api.rpc.sequence.UpdateRawUpdate import im.actor.server.api.http.HttpHandler diff --git a/actor-server/actor-core/src/main/scala/im/actor/api/rpc/DBIOResultRpc.scala b/actor-server/actor-core/src/main/scala/im/actor/api/rpc/DBIOResultRpc.scala index 27da70c4c2..bf450376cc 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/api/rpc/DBIOResultRpc.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/api/rpc/DBIOResultRpc.scala @@ -17,6 +17,7 @@ object DBIOResultRpc { def pure[A](a: A): DBIO[A] = DBIO.successful(a) def flatMap[A, B](fa: DBIO[A])(f: A ⇒ DBIO[B]): DBIO[B] = fa flatMap f override def map[A, B](fa: DBIO[A])(f: A ⇒ B): DBIO[B] = fa map f + def tailRecM[A, B](a: A)(f: A ⇒ DBIO[Either[A, B]]): DBIO[B] = defaultTailRecM(a)(f) } def point[A](a: A): Result[A] = Result[A](DBIO.successful(right(a))) diff --git a/actor-server/actor-core/src/main/scala/im/actor/api/rpc/MaybeAuthorized.scala b/actor-server/actor-core/src/main/scala/im/actor/api/rpc/MaybeAuthorized.scala index 0262b81910..8da305b930 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/api/rpc/MaybeAuthorized.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/api/rpc/MaybeAuthorized.scala @@ -36,11 +36,9 @@ case object MaybeAuthorized extends MaybeAuthorizedInstances trait MaybeAuthorizedInstances { implicit val maybeAuthorizedInstance = new Functor[MaybeAuthorized] with Monad[MaybeAuthorized] { - override def map[A, B](fa: MaybeAuthorized[A])(f: A ⇒ B): MaybeAuthorized[B] = fa.map(f) - def pure[A](a: A): MaybeAuthorized[A] = Authorized(a) - def flatMap[A, B](fa: MaybeAuthorized[A])(f: A ⇒ MaybeAuthorized[B]): MaybeAuthorized[B] = fa.flatMap(f) + def tailRecM[A, B](a: A)(f: A ⇒ MaybeAuthorized[Either[A, B]]): MaybeAuthorized[B] = defaultTailRecM(a)(f) } } diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/encryption/EncryptionApiConverters.scala b/actor-server/actor-core/src/main/scala/im/actor/server/encryption/EncryptionApiConverters.scala index 40ef212ffe..9bc89bd38b 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/encryption/EncryptionApiConverters.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/encryption/EncryptionApiConverters.scala @@ -2,7 +2,7 @@ package im.actor.server.encryption import cats.Foldable import cats.data.Xor, Xor._ -import cats.std.all._ +import cats.instances.all._ import cats.syntax.all._ import com.google.protobuf.ByteString import im.actor.api.rpc.encryption.{ ApiEncryptionKeySignature, ApiEncryptionKeyGroup, ApiEncryptionKey } diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/encryption/EncryptionExtension.scala b/actor-server/actor-core/src/main/scala/im/actor/server/encryption/EncryptionExtension.scala index 3a68237efe..75b27dcc27 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/encryption/EncryptionExtension.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/encryption/EncryptionExtension.scala @@ -3,7 +3,7 @@ package im.actor.server.encryption import akka.actor._ import akka.event.Logging import akka.http.scaladsl.util.FastFuture -import cats.std.all._ +import cats.instances.all._ import cats.syntax.all._ import cats.data.{ Xor, XorT } import im.actor.api.rpc.encryption._ diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/push/actor/ActorPush.scala b/actor-server/actor-core/src/main/scala/im/actor/server/push/actor/ActorPush.scala index b2608b994d..feb1f590ca 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/push/actor/ActorPush.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/push/actor/ActorPush.scala @@ -23,7 +23,7 @@ final case class ActorPushMessage(data: JsonObject) object ActorPushMessage { def apply(fields: Map[String, String]): ActorPushMessage = - ActorPushMessage(JsonObject.fromMap(fields mapValues Json.string)) + ActorPushMessage(JsonObject.fromMap(fields mapValues Json.fromString)) def apply(fields: (String, String)*): ActorPushMessage = ActorPushMessage(Map(fields: _*)) @@ -70,7 +70,7 @@ final class ActorPush(system: ActorSystem) extends Extension { } def deliver(seq: Int, creds: ActorPushCredentials): Unit = - deliver(ActorPushMessage(JsonObject.singleton("seq", Json.int(seq))), creds) + deliver(ActorPushMessage(JsonObject.singleton("seq", Json.fromInt(seq))), creds) def deliver(message: ActorPushMessage, creds: ActorPushCredentials): Unit = { val uri = Uri.parseAbsolute(ParserInput(creds.endpoint)) @@ -92,4 +92,4 @@ object ActorPush extends ExtensionId[ActorPush] with ExtensionIdProvider { override def createExtension(system: ExtendedActorSystem): ActorPush = new ActorPush(system) override def lookup(): ExtensionId[_ <: Extension] = ActorPush -} \ No newline at end of file +} diff --git a/actor-server/actor-http-api/src/main/scala/im/actor/server/api/http/json/JsonEncoders.scala b/actor-server/actor-http-api/src/main/scala/im/actor/server/api/http/json/JsonEncoders.scala index fbf91168d5..10f109409e 100644 --- a/actor-server/actor-http-api/src/main/scala/im/actor/server/api/http/json/JsonEncoders.scala +++ b/actor-server/actor-http-api/src/main/scala/im/actor/server/api/http/json/JsonEncoders.scala @@ -4,5 +4,5 @@ import io.circe._ import io.circe.generic.semiauto._ trait JsonEncoders { - implicit val serverInfoFormat = deriveFor[ServerInfo].encoder + implicit val serverInfoFormat = deriveEncoder[ServerInfo] } diff --git a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/auth/Helpers.scala b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/auth/Helpers.scala index 94ccace486..90a110de14 100644 --- a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/auth/Helpers.scala +++ b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/auth/Helpers.scala @@ -1,5 +1,6 @@ package im.actor.server.api.rpc.service.auth +import cats.MonadCombine import cats.data.{ NonEmptyList, Xor } import cats.syntax.all._ import im.actor.api.rpc._ @@ -9,18 +10,19 @@ import org.apache.commons.validator.routines.EmailValidator private[auth] trait Helpers extends PublicKeyHelpers { private def matchesEmail(s: String): NonEmptyList[String] Xor String = - if (EmailValidator.getInstance().isValid(s)) s.right else NonEmptyList("Should be valid email address").left + if (EmailValidator.getInstance().isValid(s)) s.right else NonEmptyList.of("Should be valid email address").left def validEmail(email: String): NonEmptyList[String] Xor String = StringUtils.nonEmptyString(email).flatMap(e ⇒ matchesEmail(e.toLowerCase)) - private implicit val listMonadCombine = new cats.MonadCombine[List] { + private implicit val listMonadCombine = new MonadCombine[List] { def pure[A](x: A): List[A] = List(x) - def combine[A](x: List[A], y: List[A]): List[A] = x ::: y def flatMap[A, B](fa: List[A])(f: (A) ⇒ List[B]): List[B] = fa flatMap f def empty[A]: List[A] = List.empty[A] + def combineK[A](x: List[A], y: List[A]): List[A] = x ::: y + def tailRecM[A, B](a: A)(f: (A) ⇒ List[Either[A, B]]): List[B] = defaultTailRecM(a)(f) } def validationFailed(errorName: String, errors: NonEmptyList[String]): RpcError = - RpcError(400, errorName, errors.unwrap.mkString(", "), false, None) + RpcError(400, errorName, errors.toList.mkString(", "), false, None) } diff --git a/actor-server/actor-runtime/src/main/scala/im/actor/concurrent/FutureResult.scala b/actor-server/actor-runtime/src/main/scala/im/actor/concurrent/FutureResult.scala index 894d92329a..6b5ae18d93 100644 --- a/actor-server/actor-runtime/src/main/scala/im/actor/concurrent/FutureResult.scala +++ b/actor-server/actor-runtime/src/main/scala/im/actor/concurrent/FutureResult.scala @@ -3,7 +3,7 @@ package im.actor.concurrent import akka.http.scaladsl.util.FastFuture import cats.data.Xor._ import cats.data.{ Xor, XorT } -import cats.std.{ EitherInstances, FutureInstances } +import cats.instances.{ EitherInstances, FutureInstances } import cats.syntax.all._ import scala.concurrent.{ ExecutionContext, Future } diff --git a/actor-server/actor-runtime/src/main/scala/im/actor/util/misc/StringUtils.scala b/actor-server/actor-runtime/src/main/scala/im/actor/util/misc/StringUtils.scala index 47118e45d2..728243ad6e 100644 --- a/actor-server/actor-runtime/src/main/scala/im/actor/util/misc/StringUtils.scala +++ b/actor-server/actor-runtime/src/main/scala/im/actor/util/misc/StringUtils.scala @@ -28,12 +28,12 @@ object StringUtils { def nonEmptyString(s: String): NonEmptyList[String] Xor String = { val trimmed = s.trim - if (trimmed.isEmpty) NonEmptyList("Should be nonempty").left else trimmed.right + if (trimmed.isEmpty) NonEmptyList.of("Should be nonempty").left else trimmed.right } def printableString(s: String): NonEmptyList[String] Xor String = { val p = Pattern.compile("\\p{Print}+", Pattern.UNICODE_CHARACTER_CLASS) - if (p.matcher(s).matches) s.right else NonEmptyList("Should contain printable characters only").left + if (p.matcher(s).matches) s.right else NonEmptyList.of("Should contain printable characters only").left } def validName(n: String): NonEmptyList[String] Xor String = diff --git a/actor-server/docker.sh b/actor-server/docker.sh index 11c3eef5ac..eb11c2c23f 100755 --- a/actor-server/docker.sh +++ b/actor-server/docker.sh @@ -1,3 +1,3 @@ #! /bin/bash -sbt docker:stage && docker build --no-cache=true -f Dockerfile -t actor-server . +sbt docker:stage && docker build --no-cache=true -f Dockerfile -t actor/server . diff --git a/actor-server/project/Build.scala b/actor-server/project/Build.scala index b3035eab28..7d27bc16d5 100644 --- a/actor-server/project/Build.scala +++ b/actor-server/project/Build.scala @@ -239,7 +239,7 @@ object Build extends sbt.Build with Versioning with Releasing with Packaging { libraryDependencies ++= Dependencies.session ) ) - .dependsOn(actorPersist, actorCore, actorCodecs, actorCore, actorRpcApi) + .dependsOn(actorCodecs, actorCore, actorPersist, actorRpcApi) lazy val actorSessionMessages = Project( id = "actor-session-messages", diff --git a/actor-server/project/Dependencies.scala b/actor-server/project/Dependencies.scala index 446beaac53..85761fdeed 100644 --- a/actor-server/project/Dependencies.scala +++ b/actor-server/project/Dependencies.scala @@ -4,15 +4,15 @@ import sbt._ object Dependencies { object V { - val actorCommons = "0.0.19" + val actorCommons = "0.0.20" val actorBotkit = "1.0.109" - val akka = "2.4.7" - val akkaHttpJson = "1.5.0" - val cats = "0.3.0" - val circe = "0.2.1" + val akka = "2.4.10" + val akkaHttpJson = "1.10.0" + val cats = "0.7.2" + val circe = "0.5.1" val kamon = "0.5.2" val slick = "3.1.1" - val slickPg = "0.13.0" + val slickPg = "0.14.3" val scalatest = "2.2.4" val shardakka = "0.1.24" val scalapbSer = "0.1.14" @@ -48,18 +48,18 @@ object Dependencies { val caffeine = "com.github.ben-manes.caffeine" % "caffeine" % "2.2.7" - val cats = "org.spire-math" %% "cats" % V.cats + val cats = "org.typelevel" %% "cats" % V.cats val circeCore = "io.circe" %% "circe-core" % V.circe val circeGeneric = "io.circe" %% "circe-generic" % V.circe - val circeParse = "io.circe" %% "circe-parse" % V.circe + val circeParse = "io.circe" %% "circe-parser" % V.circe val configs = "com.github.kxbmap" %% "configs" % "0.3.0" val dispatch = "net.databinder.dispatch" %% "dispatch-core" % "0.11.3" val javaCompat = "org.scala-lang.modules" %% "scala-java8-compat" % "0.7.0" - val playJson = "com.typesafe.play" %% "play-json" % "2.4.2" + val playJson = "com.typesafe.play" %% "play-json" % "2.5.6" val upickle = "com.lihaoyi" %% "upickle" % "0.3.6" val postgresJdbc = "org.postgresql" % "postgresql" % "9.4.1208" exclude("org.slf4j", "slf4j-simple") @@ -208,7 +208,5 @@ object Dependencies { val runtime = shared ++ Seq(akkaActor, actorConcurrent, akkaHttp, akkaSlf4j, akkaStream, akkaPersistenceJdbc, apacheCommonsCodec, caffeine, cats, jodaConvert, jodaTime, icu4j, libPhoneNumber, scalapbSer, akkaTestkit % "test", scalatest % "test") - val voximplant = shared ++ Seq(akkaActor, dispatch, playJson) - val tests = shared ++ Seq(akkaClusterSharding, amazonaws, jfairy, scalacheck, scalatest, slickTestkit, akkaTestkit, akkaMultiNodeTestkit) } From 617dc7e432c9951dd598a8bee1be3a7d0b78aa96 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Wed, 14 Sep 2016 02:33:34 +0300 Subject: [PATCH 339/414] feat(keygen): Added keygen --- actor-keygen/build.gradle | 16 + .../main/java/im/actor/keygen/Curve25519.java | 64 +++ .../im/actor/keygen/Curve25519KeyPair.java | 26 + .../src/main/java/im/actor/keygen/Hex.java | 52 ++ .../src/main/java/im/actor/keygen/Main.java | 30 + .../im/actor/keygen/curve25519/Arrays.java | 21 + .../im/actor/keygen/curve25519/Sha512.java | 13 + .../keygen/curve25519/crypto_verify_32.java | 18 + .../actor/keygen/curve25519/curve_sigs.java | 116 ++++ .../java/im/actor/keygen/curve25519/fe_0.java | 32 ++ .../java/im/actor/keygen/curve25519/fe_1.java | 30 + .../im/actor/keygen/curve25519/fe_add.java | 69 +++ .../im/actor/keygen/curve25519/fe_cmov.java | 75 +++ .../im/actor/keygen/curve25519/fe_copy.java | 40 ++ .../im/actor/keygen/curve25519/fe_cswap.java | 86 +++ .../actor/keygen/curve25519/fe_frombytes.java | 103 ++++ .../im/actor/keygen/curve25519/fe_invert.java | 197 +++++++ .../keygen/curve25519/fe_isnegative.java | 28 + .../actor/keygen/curve25519/fe_isnonzero.java | 31 ++ .../im/actor/keygen/curve25519/fe_mul.java | 296 ++++++++++ .../actor/keygen/curve25519/fe_mul121666.java | 102 ++++ .../im/actor/keygen/curve25519/fe_neg.java | 57 ++ .../actor/keygen/curve25519/fe_pow22523.java | 196 +++++++ .../im/actor/keygen/curve25519/fe_sq.java | 185 +++++++ .../im/actor/keygen/curve25519/fe_sq2.java | 196 +++++++ .../im/actor/keygen/curve25519/fe_sub.java | 69 +++ .../actor/keygen/curve25519/fe_tobytes.java | 150 +++++ .../im/actor/keygen/curve25519/ge_add.java | 120 ++++ .../im/actor/keygen/curve25519/ge_cached.java | 23 + .../curve25519/ge_double_scalarmult.java | 169 ++++++ .../actor/keygen/curve25519/ge_frombytes.java | 65 +++ .../im/actor/keygen/curve25519/ge_madd.java | 111 ++++ .../im/actor/keygen/curve25519/ge_msub.java | 111 ++++ .../im/actor/keygen/curve25519/ge_p1p1.java | 23 + .../keygen/curve25519/ge_p1p1_to_p2.java | 24 + .../keygen/curve25519/ge_p1p1_to_p3.java | 25 + .../im/actor/keygen/curve25519/ge_p2.java | 21 + .../im/actor/keygen/curve25519/ge_p2_0.java | 20 + .../im/actor/keygen/curve25519/ge_p2_dbl.java | 96 ++++ .../im/actor/keygen/curve25519/ge_p3.java | 23 + .../im/actor/keygen/curve25519/ge_p3_0.java | 21 + .../im/actor/keygen/curve25519/ge_p3_dbl.java | 24 + .../keygen/curve25519/ge_p3_to_cached.java | 30 + .../actor/keygen/curve25519/ge_p3_to_p2.java | 24 + .../keygen/curve25519/ge_p3_tobytes.java | 26 + .../actor/keygen/curve25519/ge_precomp.java | 28 + .../actor/keygen/curve25519/ge_precomp_0.java | 20 + .../curve25519/ge_precomp_base_0_7.java | 338 ++++++++++++ .../curve25519/ge_precomp_base_16_23.java | 338 ++++++++++++ .../curve25519/ge_precomp_base_24_31.java | 338 ++++++++++++ .../curve25519/ge_precomp_base_8_15.java | 339 ++++++++++++ .../keygen/curve25519/ge_scalarmult_base.java | 117 ++++ .../im/actor/keygen/curve25519/ge_sub.java | 120 ++++ .../actor/keygen/curve25519/ge_tobytes.java | 26 + .../java/im/actor/keygen/curve25519/open.java | 62 +++ .../im/actor/keygen/curve25519/sc_muladd.java | 516 ++++++++++++++++++ .../im/actor/keygen/curve25519/sc_reduce.java | 377 +++++++++++++ .../actor/keygen/curve25519/scalarmult.java | 200 +++++++ .../keygen/curve25519/sign_modified.java | 62 +++ settings.gradle | 6 + 60 files changed, 6141 insertions(+) create mode 100644 actor-keygen/build.gradle create mode 100644 actor-keygen/src/main/java/im/actor/keygen/Curve25519.java create mode 100644 actor-keygen/src/main/java/im/actor/keygen/Curve25519KeyPair.java create mode 100644 actor-keygen/src/main/java/im/actor/keygen/Hex.java create mode 100644 actor-keygen/src/main/java/im/actor/keygen/Main.java create mode 100755 actor-keygen/src/main/java/im/actor/keygen/curve25519/Arrays.java create mode 100755 actor-keygen/src/main/java/im/actor/keygen/curve25519/Sha512.java create mode 100755 actor-keygen/src/main/java/im/actor/keygen/curve25519/crypto_verify_32.java create mode 100755 actor-keygen/src/main/java/im/actor/keygen/curve25519/curve_sigs.java create mode 100755 actor-keygen/src/main/java/im/actor/keygen/curve25519/fe_0.java create mode 100755 actor-keygen/src/main/java/im/actor/keygen/curve25519/fe_1.java create mode 100755 actor-keygen/src/main/java/im/actor/keygen/curve25519/fe_add.java create mode 100755 actor-keygen/src/main/java/im/actor/keygen/curve25519/fe_cmov.java create mode 100755 actor-keygen/src/main/java/im/actor/keygen/curve25519/fe_copy.java create mode 100755 actor-keygen/src/main/java/im/actor/keygen/curve25519/fe_cswap.java create mode 100755 actor-keygen/src/main/java/im/actor/keygen/curve25519/fe_frombytes.java create mode 100755 actor-keygen/src/main/java/im/actor/keygen/curve25519/fe_invert.java create mode 100755 actor-keygen/src/main/java/im/actor/keygen/curve25519/fe_isnegative.java create mode 100755 actor-keygen/src/main/java/im/actor/keygen/curve25519/fe_isnonzero.java create mode 100755 actor-keygen/src/main/java/im/actor/keygen/curve25519/fe_mul.java create mode 100755 actor-keygen/src/main/java/im/actor/keygen/curve25519/fe_mul121666.java create mode 100755 actor-keygen/src/main/java/im/actor/keygen/curve25519/fe_neg.java create mode 100755 actor-keygen/src/main/java/im/actor/keygen/curve25519/fe_pow22523.java create mode 100755 actor-keygen/src/main/java/im/actor/keygen/curve25519/fe_sq.java create mode 100755 actor-keygen/src/main/java/im/actor/keygen/curve25519/fe_sq2.java create mode 100755 actor-keygen/src/main/java/im/actor/keygen/curve25519/fe_sub.java create mode 100755 actor-keygen/src/main/java/im/actor/keygen/curve25519/fe_tobytes.java create mode 100755 actor-keygen/src/main/java/im/actor/keygen/curve25519/ge_add.java create mode 100755 actor-keygen/src/main/java/im/actor/keygen/curve25519/ge_cached.java create mode 100755 actor-keygen/src/main/java/im/actor/keygen/curve25519/ge_double_scalarmult.java create mode 100755 actor-keygen/src/main/java/im/actor/keygen/curve25519/ge_frombytes.java create mode 100755 actor-keygen/src/main/java/im/actor/keygen/curve25519/ge_madd.java create mode 100755 actor-keygen/src/main/java/im/actor/keygen/curve25519/ge_msub.java create mode 100755 actor-keygen/src/main/java/im/actor/keygen/curve25519/ge_p1p1.java create mode 100755 actor-keygen/src/main/java/im/actor/keygen/curve25519/ge_p1p1_to_p2.java create mode 100755 actor-keygen/src/main/java/im/actor/keygen/curve25519/ge_p1p1_to_p3.java create mode 100755 actor-keygen/src/main/java/im/actor/keygen/curve25519/ge_p2.java create mode 100755 actor-keygen/src/main/java/im/actor/keygen/curve25519/ge_p2_0.java create mode 100755 actor-keygen/src/main/java/im/actor/keygen/curve25519/ge_p2_dbl.java create mode 100755 actor-keygen/src/main/java/im/actor/keygen/curve25519/ge_p3.java create mode 100755 actor-keygen/src/main/java/im/actor/keygen/curve25519/ge_p3_0.java create mode 100755 actor-keygen/src/main/java/im/actor/keygen/curve25519/ge_p3_dbl.java create mode 100755 actor-keygen/src/main/java/im/actor/keygen/curve25519/ge_p3_to_cached.java create mode 100755 actor-keygen/src/main/java/im/actor/keygen/curve25519/ge_p3_to_p2.java create mode 100755 actor-keygen/src/main/java/im/actor/keygen/curve25519/ge_p3_tobytes.java create mode 100755 actor-keygen/src/main/java/im/actor/keygen/curve25519/ge_precomp.java create mode 100755 actor-keygen/src/main/java/im/actor/keygen/curve25519/ge_precomp_0.java create mode 100755 actor-keygen/src/main/java/im/actor/keygen/curve25519/ge_precomp_base_0_7.java create mode 100755 actor-keygen/src/main/java/im/actor/keygen/curve25519/ge_precomp_base_16_23.java create mode 100755 actor-keygen/src/main/java/im/actor/keygen/curve25519/ge_precomp_base_24_31.java create mode 100755 actor-keygen/src/main/java/im/actor/keygen/curve25519/ge_precomp_base_8_15.java create mode 100755 actor-keygen/src/main/java/im/actor/keygen/curve25519/ge_scalarmult_base.java create mode 100755 actor-keygen/src/main/java/im/actor/keygen/curve25519/ge_sub.java create mode 100755 actor-keygen/src/main/java/im/actor/keygen/curve25519/ge_tobytes.java create mode 100755 actor-keygen/src/main/java/im/actor/keygen/curve25519/open.java create mode 100755 actor-keygen/src/main/java/im/actor/keygen/curve25519/sc_muladd.java create mode 100755 actor-keygen/src/main/java/im/actor/keygen/curve25519/sc_reduce.java create mode 100755 actor-keygen/src/main/java/im/actor/keygen/curve25519/scalarmult.java create mode 100755 actor-keygen/src/main/java/im/actor/keygen/curve25519/sign_modified.java diff --git a/actor-keygen/build.gradle b/actor-keygen/build.gradle new file mode 100644 index 0000000000..0a0000e3bb --- /dev/null +++ b/actor-keygen/build.gradle @@ -0,0 +1,16 @@ +apply plugin: 'application' + +mainClassName = "im.actor.keygen.Main" + +dependencies { + compile group: 'commons-io', name: 'commons-io', version: '2.5' + testCompile "junit:junit:4.11" +} + +allprojects { + tasks.withType(JavaCompile) { + sourceCompatibility = "1.8" + targetCompatibility = "1.8" + } +} + diff --git a/actor-keygen/src/main/java/im/actor/keygen/Curve25519.java b/actor-keygen/src/main/java/im/actor/keygen/Curve25519.java new file mode 100644 index 0000000000..800380502b --- /dev/null +++ b/actor-keygen/src/main/java/im/actor/keygen/Curve25519.java @@ -0,0 +1,64 @@ +package im.actor.keygen; + +import java.security.DigestException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +import im.actor.keygen.curve25519.curve_sigs; + +public final class Curve25519 { + + /** + * Generating KeyPair + * + * @param randomBytes 32 random bytes + * @return generated key pair + */ + public static Curve25519KeyPair keyGen(byte[] randomBytes) throws NoSuchAlgorithmException, DigestException { + byte[] privateKey = keyGenPrivate(randomBytes); + byte[] publicKey = keyGenPublic(privateKey); + return new Curve25519KeyPair(publicKey, privateKey); + } + + /** + * Generating private key. Source: https://cr.yp.to/ecdh.html + * + * @param randomBytes random bytes (32+ bytes) + * @return generated private key + */ + public static byte[] keyGenPrivate(byte[] randomBytes) throws NoSuchAlgorithmException, DigestException { + + if (randomBytes.length < 32) { + throw new RuntimeException("Random bytes too small"); + } + + // Hashing Random Bytes instead of using random bytes directly + // Just in case as reference ed255519 implementation do same + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + digest.digest(randomBytes, 0, randomBytes.length); + byte[] privateKey = digest.digest(); + + // Performing bit's flipping + privateKey[0] &= 248; + privateKey[31] &= 127; + privateKey[31] |= 64; + + return privateKey; + } + + /** + * Building public key with private key + * + * @param privateKey private key + * @return generated public key + */ + public static byte[] keyGenPublic(byte[] privateKey) { + byte[] publicKey = new byte[32]; + curve_sigs.curve25519_keygen(publicKey, privateKey); + return publicKey; + } + + private Curve25519() { + + } +} \ No newline at end of file diff --git a/actor-keygen/src/main/java/im/actor/keygen/Curve25519KeyPair.java b/actor-keygen/src/main/java/im/actor/keygen/Curve25519KeyPair.java new file mode 100644 index 0000000000..ac1851c029 --- /dev/null +++ b/actor-keygen/src/main/java/im/actor/keygen/Curve25519KeyPair.java @@ -0,0 +1,26 @@ +package im.actor.keygen; + +// Disabling Bounds checks for speeding up calculations + +/*-[ +#define J2OBJC_DISABLE_ARRAY_BOUND_CHECKS 1 +]-*/ + +public class Curve25519KeyPair { + + private byte[] publicKey; + private byte[] privateKey; + + public Curve25519KeyPair(byte[] publicKey, byte[] privateKey) { + this.publicKey = publicKey; + this.privateKey = privateKey; + } + + public byte[] getPublicKey() { + return publicKey; + } + + public byte[] getPrivateKey() { + return privateKey; + } +} \ No newline at end of file diff --git a/actor-keygen/src/main/java/im/actor/keygen/Hex.java b/actor-keygen/src/main/java/im/actor/keygen/Hex.java new file mode 100644 index 0000000000..c40f423069 --- /dev/null +++ b/actor-keygen/src/main/java/im/actor/keygen/Hex.java @@ -0,0 +1,52 @@ +package im.actor.keygen; + +public class Hex { + + final protected static char[] HEXES_SMALL = "0123456789abcdef".toCharArray(); + + private static final String HEXES = "0123456789ABCDEF"; + + public static String toHex(byte[] raw) { + final StringBuilder hex = new StringBuilder(2 * raw.length); + for (final byte b : raw) { + hex.append(HEXES.charAt((b & 0xF0) >> 4)).append(HEXES.charAt((b & 0x0F))); + } + return hex.toString(); + } + + + /** + * Calculating lowcase hex string + * + * @param bytes data for hex + * @return hex string + */ + public static String hex(byte[] bytes) { + char[] hexChars = new char[bytes.length * 2]; + for (int j = 0; j < bytes.length; j++) { + int v = bytes[j] & 0xFF; + hexChars[j * 2] = HEXES_SMALL[v >>> 4]; + hexChars[j * 2 + 1] = HEXES_SMALL[v & 0x0F]; + } + return new String(hexChars); + } + + private static int fromHexShort(char a) { + if (a >= '0' && a <= '9') { + return a - '0'; + } + if (a >= 'a' && a <= 'f') { + return 10 + (a - 'a'); + } + + throw new RuntimeException(); + } + + public static byte[] fromHex(String hex) { + byte[] res = new byte[hex.length() / 2]; + for (int i = 0; i < res.length; i++) { + res[i] = (byte) ((fromHexShort(hex.charAt(i * 2)) << 4) + fromHexShort(hex.charAt(i * 2 + 1))); + } + return res; + } +} diff --git a/actor-keygen/src/main/java/im/actor/keygen/Main.java b/actor-keygen/src/main/java/im/actor/keygen/Main.java new file mode 100644 index 0000000000..ad75ae443f --- /dev/null +++ b/actor-keygen/src/main/java/im/actor/keygen/Main.java @@ -0,0 +1,30 @@ +package im.actor.keygen; + +import org.apache.commons.io.FileUtils; + +import java.io.File; +import java.io.IOException; +import java.security.DigestException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; + +public class Main { + public static void main(String[] args) throws DigestException, NoSuchAlgorithmException, IOException { + SecureRandom secureRandom = new SecureRandom(); + if (!new File("keys").exists()) { + new File("keys").mkdir(); + } + for (int i = 0; i < 4; i++) { + File pubFile = new File("keys/actor-key-" + i + ".pub"); + File keyFile = new File("keys/actor-key-" + i + ".key"); + if (pubFile.exists() && keyFile.exists()) { + System.out.println("Key #" + i + " exists. Skipping..."); + continue; + } + Curve25519KeyPair keyPair = Curve25519.keyGen(secureRandom.generateSeed(64)); + FileUtils.writeByteArrayToFile(pubFile, keyPair.getPublicKey()); + FileUtils.writeByteArrayToFile(keyFile, keyPair.getPrivateKey()); + } + System.out.println("Shared Secret: " + Hex.toHex(secureRandom.generateSeed(64))); + } +} \ No newline at end of file diff --git a/actor-keygen/src/main/java/im/actor/keygen/curve25519/Arrays.java b/actor-keygen/src/main/java/im/actor/keygen/curve25519/Arrays.java new file mode 100755 index 0000000000..9441c32195 --- /dev/null +++ b/actor-keygen/src/main/java/im/actor/keygen/curve25519/Arrays.java @@ -0,0 +1,21 @@ +package im.actor.keygen.curve25519; + +// Disabling Bounds checks for speeding up calculations + +/*-[ +#define J2OBJC_DISABLE_ARRAY_BOUND_CHECKS 1 +]-*/ + +public class Arrays { + /** + * Assigns the specified byte value to each element of the specified array + * of bytes. + * + * @param a the array to be filled + * @param val the value to be stored in all elements of the array + */ + public static void fill(byte[] a, byte val) { + for (int i = 0, len = a.length; i < len; i++) + a[i] = val; + } +} diff --git a/actor-keygen/src/main/java/im/actor/keygen/curve25519/Sha512.java b/actor-keygen/src/main/java/im/actor/keygen/curve25519/Sha512.java new file mode 100755 index 0000000000..f437c27b88 --- /dev/null +++ b/actor-keygen/src/main/java/im/actor/keygen/curve25519/Sha512.java @@ -0,0 +1,13 @@ +package im.actor.keygen.curve25519; + +// Disabling Bounds checks for speeding up calculations + +/*-[ +#define J2OBJC_DISABLE_ARRAY_BOUND_CHECKS 1 +]-*/ + +public interface Sha512 { + + void calculateDigest(byte[] out, byte[] in, long length); + +} diff --git a/actor-keygen/src/main/java/im/actor/keygen/curve25519/crypto_verify_32.java b/actor-keygen/src/main/java/im/actor/keygen/curve25519/crypto_verify_32.java new file mode 100755 index 0000000000..537ef28e40 --- /dev/null +++ b/actor-keygen/src/main/java/im/actor/keygen/curve25519/crypto_verify_32.java @@ -0,0 +1,18 @@ +package im.actor.keygen.curve25519; + +// Disabling Bounds checks for speeding up calculations + +/*-[ +#define J2OBJC_DISABLE_ARRAY_BOUND_CHECKS 1 +]-*/ + +public class crypto_verify_32 { + + public static int crypto_verify_32(byte[] x, byte[] y) { + int differentbits = 0; + for (int count = 0; count < 32; count++) { + differentbits |= (x[count] ^ y[count]); + } + return differentbits; + } +} diff --git a/actor-keygen/src/main/java/im/actor/keygen/curve25519/curve_sigs.java b/actor-keygen/src/main/java/im/actor/keygen/curve25519/curve_sigs.java new file mode 100755 index 0000000000..5a12dc7a91 --- /dev/null +++ b/actor-keygen/src/main/java/im/actor/keygen/curve25519/curve_sigs.java @@ -0,0 +1,116 @@ +package im.actor.keygen.curve25519; + +// Disabling Bounds checks for speeding up calculations + +/*-[ +#define J2OBJC_DISABLE_ARRAY_BOUND_CHECKS 1 +]-*/ + +public class curve_sigs { + + public static void curve25519_keygen(byte[] curve25519_pubkey_out, + byte[] curve25519_privkey_in) { + ge_p3 ed = new ge_p3(); /* Ed25519 pubkey point */ + int[] ed_y = new int[10]; + int[] ed_y_plus_one = new int[10]; + int[] one_minus_ed_y = new int[10]; + int[] inv_one_minus_ed_y = new int[10]; + int[] mont_x = new int[10]; + + /* Perform a fixed-base multiplication of the Edwards base point, + (which is efficient due to precalculated tables), then convert + to the Curve25519 montgomery-format public key. In particular, + convert Curve25519's "montgomery" x-coordinate into an Ed25519 + "edwards" y-coordinate: + + mont_x = (ed_y + 1) / (1 - ed_y) + + with projective coordinates: + + mont_x = (ed_y + ed_z) / (ed_z - ed_y) + + NOTE: ed_y=1 is converted to mont_x=0 since fe_invert is mod-exp + */ + + ge_scalarmult_base.ge_scalarmult_base(ed, curve25519_privkey_in); + fe_add.fe_add(ed_y_plus_one, ed.Y, ed.Z); + fe_sub.fe_sub(one_minus_ed_y, ed.Z, ed.Y); + fe_invert.fe_invert(inv_one_minus_ed_y, one_minus_ed_y); + fe_mul.fe_mul(mont_x, ed_y_plus_one, inv_one_minus_ed_y); + fe_tobytes.fe_tobytes(curve25519_pubkey_out, mont_x); + } + + public static int curve25519_sign(Sha512 sha512provider, byte[] signature_out, + byte[] curve25519_privkey, + byte[] msg, int msg_len, + byte[] random) { + ge_p3 ed_pubkey_point = new ge_p3(); /* Ed25519 pubkey point */ + byte[] ed_pubkey = new byte[32]; /* Ed25519 encoded pubkey */ + byte[] sigbuf = new byte[msg_len + 128]; /* working buffer */ + byte sign_bit = 0; + + /* Convert the Curve25519 privkey to an Ed25519 public key */ + ge_scalarmult_base.ge_scalarmult_base(ed_pubkey_point, curve25519_privkey); + ge_p3_tobytes.ge_p3_tobytes(ed_pubkey, ed_pubkey_point); + sign_bit = (byte) (ed_pubkey[31] & 0x80); + + /* Perform an Ed25519 signature with explicit private key */ + sign_modified.crypto_sign_modified(sha512provider, sigbuf, msg, msg_len, curve25519_privkey, + ed_pubkey, random); + System.arraycopy(sigbuf, 0, signature_out, 0, 64); + + /* Encode the sign bit into signature (in unused high bit of S) */ + signature_out[63] &= 0x7F; /* bit should be zero already, but just in case */ + signature_out[63] |= sign_bit; + return 0; + } + + public static int curve25519_verify(Sha512 sha512provider, byte[] signature, + byte[] curve25519_pubkey, + byte[] msg, int msg_len) { + int[] mont_x = new int[10]; + int[] mont_x_minus_one = new int[10]; + int[] mont_x_plus_one = new int[10]; + int[] inv_mont_x_plus_one = new int[10]; + int[] one = new int[10]; + int[] ed_y = new int[10]; + byte[] ed_pubkey = new byte[32]; + long some_retval = 0; + byte[] verifybuf = new byte[msg_len + 64]; /* working buffer */ + byte[] verifybuf2 = new byte[msg_len + 64]; /* working buffer #2 */ + + /* Convert the Curve25519 public key into an Ed25519 public key. In + particular, convert Curve25519's "montgomery" x-coordinate into an + Ed25519 "edwards" y-coordinate: + + ed_y = (mont_x - 1) / (mont_x + 1) + + NOTE: mont_x=-1 is converted to ed_y=0 since fe_invert is mod-exp + + Then move the sign bit into the pubkey from the signature. + */ + fe_frombytes.fe_frombytes(mont_x, curve25519_pubkey); + fe_1.fe_1(one); + fe_sub.fe_sub(mont_x_minus_one, mont_x, one); + fe_add.fe_add(mont_x_plus_one, mont_x, one); + fe_invert.fe_invert(inv_mont_x_plus_one, mont_x_plus_one); + fe_mul.fe_mul(ed_y, mont_x_minus_one, inv_mont_x_plus_one); + fe_tobytes.fe_tobytes(ed_pubkey, ed_y); + + /* Copy the sign bit, and remove it from signature */ + ed_pubkey[31] &= 0x7F; /* bit should be zero already, but just in case */ + ed_pubkey[31] |= (signature[63] & 0x80); + System.arraycopy(signature, 0, verifybuf, 0, 64); + verifybuf[63] &= 0x7F; + + System.arraycopy(msg, 0, verifybuf, 64, (int) msg_len); + + /* Then perform a normal Ed25519 verification, return 0 on success */ + /* The below call has a strange API: */ + /* verifybuf = R || S || message */ + /* verifybuf2 = java to next call gets a copy of verifybuf, S gets + replaced with pubkey for hashing, then the whole thing gets zeroized + (if bad sig), or contains a copy of msg (good sig) */ + return open.crypto_sign_open(sha512provider, verifybuf2, some_retval, verifybuf, 64 + msg_len, ed_pubkey); + } +} diff --git a/actor-keygen/src/main/java/im/actor/keygen/curve25519/fe_0.java b/actor-keygen/src/main/java/im/actor/keygen/curve25519/fe_0.java new file mode 100755 index 0000000000..6f56360f69 --- /dev/null +++ b/actor-keygen/src/main/java/im/actor/keygen/curve25519/fe_0.java @@ -0,0 +1,32 @@ +package im.actor.keygen.curve25519; + +// Disabling Bounds checks for speeding up calculations + +/*-[ +#define J2OBJC_DISABLE_ARRAY_BOUND_CHECKS 1 +]-*/ + +public class fe_0 { + +//CONVERT #include "fe.h" + +/* +h = 0 +*/ + +public static void fe_0(int[] h) +{ + h[0] = 0; + h[1] = 0; + h[2] = 0; + h[3] = 0; + h[4] = 0; + h[5] = 0; + h[6] = 0; + h[7] = 0; + h[8] = 0; + h[9] = 0; +} + + +} diff --git a/actor-keygen/src/main/java/im/actor/keygen/curve25519/fe_1.java b/actor-keygen/src/main/java/im/actor/keygen/curve25519/fe_1.java new file mode 100755 index 0000000000..f472c79090 --- /dev/null +++ b/actor-keygen/src/main/java/im/actor/keygen/curve25519/fe_1.java @@ -0,0 +1,30 @@ +package im.actor.keygen.curve25519; + +// Disabling Bounds checks for speeding up calculations + +/*-[ +#define J2OBJC_DISABLE_ARRAY_BOUND_CHECKS 1 +]-*/ + +public class fe_1 { + + //CONVERT #include "fe.h" + + /* + h = 1 + */ + public static void fe_1(int[] h) { + h[0] = 1; + h[1] = 0; + h[2] = 0; + h[3] = 0; + h[4] = 0; + h[5] = 0; + h[6] = 0; + h[7] = 0; + h[8] = 0; + h[9] = 0; + } + + +} diff --git a/actor-keygen/src/main/java/im/actor/keygen/curve25519/fe_add.java b/actor-keygen/src/main/java/im/actor/keygen/curve25519/fe_add.java new file mode 100755 index 0000000000..e7b49b8db8 --- /dev/null +++ b/actor-keygen/src/main/java/im/actor/keygen/curve25519/fe_add.java @@ -0,0 +1,69 @@ +package im.actor.keygen.curve25519; + +// Disabling Bounds checks for speeding up calculations + +/*-[ +#define J2OBJC_DISABLE_ARRAY_BOUND_CHECKS 1 +]-*/ + +public class fe_add { + +//CONVERT #include "fe.h" + +/* +h = f + g +Can overlap h with f or g. + +Preconditions: + |f| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. + |g| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. + +Postconditions: + |h| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc. +*/ + + public static void fe_add(int[] h, int[] f, int[] g) { + int f0 = f[0]; + int f1 = f[1]; + int f2 = f[2]; + int f3 = f[3]; + int f4 = f[4]; + int f5 = f[5]; + int f6 = f[6]; + int f7 = f[7]; + int f8 = f[8]; + int f9 = f[9]; + int g0 = g[0]; + int g1 = g[1]; + int g2 = g[2]; + int g3 = g[3]; + int g4 = g[4]; + int g5 = g[5]; + int g6 = g[6]; + int g7 = g[7]; + int g8 = g[8]; + int g9 = g[9]; + int h0 = f0 + g0; + int h1 = f1 + g1; + int h2 = f2 + g2; + int h3 = f3 + g3; + int h4 = f4 + g4; + int h5 = f5 + g5; + int h6 = f6 + g6; + int h7 = f7 + g7; + int h8 = f8 + g8; + int h9 = f9 + g9; + h[0] = (int) h0; + h[1] = (int) h1; + h[2] = (int) h2; + h[3] = (int) h3; + h[4] = (int) h4; + h[5] = (int) h5; + h[6] = (int) h6; + h[7] = (int) h7; + h[8] = (int) h8; + h[9] = (int) h9; + } + + +} diff --git a/actor-keygen/src/main/java/im/actor/keygen/curve25519/fe_cmov.java b/actor-keygen/src/main/java/im/actor/keygen/curve25519/fe_cmov.java new file mode 100755 index 0000000000..195de36204 --- /dev/null +++ b/actor-keygen/src/main/java/im/actor/keygen/curve25519/fe_cmov.java @@ -0,0 +1,75 @@ +package im.actor.keygen.curve25519; + +// Disabling Bounds checks for speeding up calculations + +/*-[ +#define J2OBJC_DISABLE_ARRAY_BOUND_CHECKS 1 +]-*/ + +public class fe_cmov { + + //CONVERT #include "fe.h" + + /* + Replace (f,g) with (g,g) if b == 1; + replace (f,g) with (f,g) if b == 0. + + Preconditions: b in {0,1}. + */ + + public static void fe_cmov(int[] f, int[] g, int b) { + int f0 = f[0]; + int f1 = f[1]; + int f2 = f[2]; + int f3 = f[3]; + int f4 = f[4]; + int f5 = f[5]; + int f6 = f[6]; + int f7 = f[7]; + int f8 = f[8]; + int f9 = f[9]; + int g0 = g[0]; + int g1 = g[1]; + int g2 = g[2]; + int g3 = g[3]; + int g4 = g[4]; + int g5 = g[5]; + int g6 = g[6]; + int g7 = g[7]; + int g8 = g[8]; + int g9 = g[9]; + int x0 = f0 ^ g0; + int x1 = f1 ^ g1; + int x2 = f2 ^ g2; + int x3 = f3 ^ g3; + int x4 = f4 ^ g4; + int x5 = f5 ^ g5; + int x6 = f6 ^ g6; + int x7 = f7 ^ g7; + int x8 = f8 ^ g8; + int x9 = f9 ^ g9; + b = -b; + x0 &= b; + x1 &= b; + x2 &= b; + x3 &= b; + x4 &= b; + x5 &= b; + x6 &= b; + x7 &= b; + x8 &= b; + x9 &= b; + f[0] = f0 ^ x0; + f[1] = f1 ^ x1; + f[2] = f2 ^ x2; + f[3] = f3 ^ x3; + f[4] = f4 ^ x4; + f[5] = f5 ^ x5; + f[6] = f6 ^ x6; + f[7] = f7 ^ x7; + f[8] = f8 ^ x8; + f[9] = f9 ^ x9; + } + + +} diff --git a/actor-keygen/src/main/java/im/actor/keygen/curve25519/fe_copy.java b/actor-keygen/src/main/java/im/actor/keygen/curve25519/fe_copy.java new file mode 100755 index 0000000000..a569c93db5 --- /dev/null +++ b/actor-keygen/src/main/java/im/actor/keygen/curve25519/fe_copy.java @@ -0,0 +1,40 @@ +package im.actor.keygen.curve25519; + +// Disabling Bounds checks for speeding up calculations + +/*-[ +#define J2OBJC_DISABLE_ARRAY_BOUND_CHECKS 1 +]-*/ + +public class fe_copy { + +//CONVERT #include "fe.h" + + /* + h = f + */ + public static void fe_copy(int[] h, int[] f) { + int f0 = f[0]; + int f1 = f[1]; + int f2 = f[2]; + int f3 = f[3]; + int f4 = f[4]; + int f5 = f[5]; + int f6 = f[6]; + int f7 = f[7]; + int f8 = f[8]; + int f9 = f[9]; + h[0] = f0; + h[1] = f1; + h[2] = f2; + h[3] = f3; + h[4] = f4; + h[5] = f5; + h[6] = f6; + h[7] = f7; + h[8] = f8; + h[9] = f9; + } + + +} diff --git a/actor-keygen/src/main/java/im/actor/keygen/curve25519/fe_cswap.java b/actor-keygen/src/main/java/im/actor/keygen/curve25519/fe_cswap.java new file mode 100755 index 0000000000..e1bb8a793d --- /dev/null +++ b/actor-keygen/src/main/java/im/actor/keygen/curve25519/fe_cswap.java @@ -0,0 +1,86 @@ +package im.actor.keygen.curve25519; + +// Disabling Bounds checks for speeding up calculations + +/*-[ +#define J2OBJC_DISABLE_ARRAY_BOUND_CHECKS 1 +]-*/ + +public class fe_cswap { + +//CONVERT #include +//CONVERT #include "fe.h" + +/* +Replace (f,g) with (g,f) if b == 1; +replace (f,g) with (f,g) if b == 0. + +Preconditions: b in {0,1}. +*/ + + public static void fe_cswap(int[] f, int[] g, int b) { + int f0 = f[0]; + int f1 = f[1]; + int f2 = f[2]; + int f3 = f[3]; + int f4 = f[4]; + int f5 = f[5]; + int f6 = f[6]; + int f7 = f[7]; + int f8 = f[8]; + int f9 = f[9]; + int g0 = g[0]; + int g1 = g[1]; + int g2 = g[2]; + int g3 = g[3]; + int g4 = g[4]; + int g5 = g[5]; + int g6 = g[6]; + int g7 = g[7]; + int g8 = g[8]; + int g9 = g[9]; + int x0 = f0 ^ g0; + int x1 = f1 ^ g1; + int x2 = f2 ^ g2; + int x3 = f3 ^ g3; + int x4 = f4 ^ g4; + int x5 = f5 ^ g5; + int x6 = f6 ^ g6; + int x7 = f7 ^ g7; + int x8 = f8 ^ g8; + int x9 = f9 ^ g9; + b = -b; + x0 &= b; + x1 &= b; + x2 &= b; + x3 &= b; + x4 &= b; + x5 &= b; + x6 &= b; + x7 &= b; + x8 &= b; + x9 &= b; + f[0] = f0 ^ x0; + f[1] = f1 ^ x1; + f[2] = f2 ^ x2; + f[3] = f3 ^ x3; + f[4] = f4 ^ x4; + f[5] = f5 ^ x5; + f[6] = f6 ^ x6; + f[7] = f7 ^ x7; + f[8] = f8 ^ x8; + f[9] = f9 ^ x9; + g[0] = g0 ^ x0; + g[1] = g1 ^ x1; + g[2] = g2 ^ x2; + g[3] = g3 ^ x3; + g[4] = g4 ^ x4; + g[5] = g5 ^ x5; + g[6] = g6 ^ x6; + g[7] = g7 ^ x7; + g[8] = g8 ^ x8; + g[9] = g9 ^ x9; + } + + +} diff --git a/actor-keygen/src/main/java/im/actor/keygen/curve25519/fe_frombytes.java b/actor-keygen/src/main/java/im/actor/keygen/curve25519/fe_frombytes.java new file mode 100755 index 0000000000..7785c1f30e --- /dev/null +++ b/actor-keygen/src/main/java/im/actor/keygen/curve25519/fe_frombytes.java @@ -0,0 +1,103 @@ +package im.actor.keygen.curve25519; + +// Disabling Bounds checks for speeding up calculations + +/*-[ +#define J2OBJC_DISABLE_ARRAY_BOUND_CHECKS 1 +]-*/ + +public class fe_frombytes { + +//CONVERT #include "fe.h" +//CONVERT #include "long.h" +//CONVERT #include "long.h" + + public static long load_3(byte[] in, int index) { + long result; + result = ((long) in[index + 0]) & 0xFF; + result |= (((long) in[index + 1]) << 8) & 0xFF00; + result |= (((long) in[index + 2]) << 16) & 0xFF0000; + return result; + } + + public static long load_4(byte[] in, int index) { + long result; + result = (((long) in[index + 0]) & 0xFF); + result |= ((((long) in[index + 1]) << 8) & 0xFF00); + result |= ((((long) in[index + 2]) << 16) & 0xFF0000); + result |= ((((long) in[index + 3]) << 24) & 0xFF000000L); + return result; + } + +/* +Ignores top bit of h. +*/ + + public static void fe_frombytes(int[] h, byte[] s) { + long h0 = load_4(s, 0); + long h1 = load_3(s, 4) << 6; + long h2 = load_3(s, 7) << 5; + long h3 = load_3(s, 10) << 3; + long h4 = load_3(s, 13) << 2; + long h5 = load_4(s, 16); + long h6 = load_3(s, 20) << 7; + long h7 = load_3(s, 23) << 5; + long h8 = load_3(s, 26) << 4; + long h9 = (load_3(s, 29) & 8388607) << 2; + long carry0; + long carry1; + long carry2; + long carry3; + long carry4; + long carry5; + long carry6; + long carry7; + long carry8; + long carry9; + + carry9 = (h9 + (long) (1 << 24)) >> 25; + h0 += carry9 * 19; + h9 -= carry9 << 25; + carry1 = (h1 + (long) (1 << 24)) >> 25; + h2 += carry1; + h1 -= carry1 << 25; + carry3 = (h3 + (long) (1 << 24)) >> 25; + h4 += carry3; + h3 -= carry3 << 25; + carry5 = (h5 + (long) (1 << 24)) >> 25; + h6 += carry5; + h5 -= carry5 << 25; + carry7 = (h7 + (long) (1 << 24)) >> 25; + h8 += carry7; + h7 -= carry7 << 25; + + carry0 = (h0 + (long) (1 << 25)) >> 26; + h1 += carry0; + h0 -= carry0 << 26; + carry2 = (h2 + (long) (1 << 25)) >> 26; + h3 += carry2; + h2 -= carry2 << 26; + carry4 = (h4 + (long) (1 << 25)) >> 26; + h5 += carry4; + h4 -= carry4 << 26; + carry6 = (h6 + (long) (1 << 25)) >> 26; + h7 += carry6; + h6 -= carry6 << 26; + carry8 = (h8 + (long) (1 << 25)) >> 26; + h9 += carry8; + h8 -= carry8 << 26; + + h[0] = (int) h0; + h[1] = (int) h1; + h[2] = (int) h2; + h[3] = (int) h3; + h[4] = (int) h4; + h[5] = (int) h5; + h[6] = (int) h6; + h[7] = (int) h7; + h[8] = (int) h8; + h[9] = (int) h9; + } + + +} diff --git a/actor-keygen/src/main/java/im/actor/keygen/curve25519/fe_invert.java b/actor-keygen/src/main/java/im/actor/keygen/curve25519/fe_invert.java new file mode 100755 index 0000000000..1c84670b72 --- /dev/null +++ b/actor-keygen/src/main/java/im/actor/keygen/curve25519/fe_invert.java @@ -0,0 +1,197 @@ +package im.actor.keygen.curve25519; + +// Disabling Bounds checks for speeding up calculations + +/*-[ +#define J2OBJC_DISABLE_ARRAY_BOUND_CHECKS 1 +]-*/ + +public class fe_invert { + +//CONVERT #include "fe.h" + + public static void fe_invert(int[] out, int[] z) { + int[] t0 = new int[10]; + int[] t1 = new int[10]; + int[] t2 = new int[10]; + int[] t3 = new int[10]; + int i; + +//CONVERT #include "pow225521.h" + +/* qhasm: fe z1 */ + +/* qhasm: fe z2 */ + +/* qhasm: fe z8 */ + +/* qhasm: fe z9 */ + +/* qhasm: fe z11 */ + +/* qhasm: fe z22 */ + +/* qhasm: fe z_5_0 */ + +/* qhasm: fe z_10_5 */ + +/* qhasm: fe z_10_0 */ + +/* qhasm: fe z_20_10 */ + +/* qhasm: fe z_20_0 */ + +/* qhasm: fe z_40_20 */ + +/* qhasm: fe z_40_0 */ + +/* qhasm: fe z_50_10 */ + +/* qhasm: fe z_50_0 */ + +/* qhasm: fe z_100_50 */ + +/* qhasm: fe z_100_0 */ + +/* qhasm: fe z_200_100 */ + +/* qhasm: fe z_200_0 */ + +/* qhasm: fe z_250_50 */ + +/* qhasm: fe z_250_0 */ + +/* qhasm: fe z_255_5 */ + +/* qhasm: fe z_255_21 */ + +/* qhasm: enter pow225521 */ + +/* qhasm: z2 = z1^2^1 */ +/* asm 1: fe_sq.fe_sq(>z2=fe#1,z2=fe#1,>z2=fe#1); */ +/* asm 2: fe_sq.fe_sq(>z2=t0,z2=t0,>z2=t0); */ + fe_sq.fe_sq(t0, z); + for (i = 1; i < 1; ++i) fe_sq.fe_sq(t0, t0); + +/* qhasm: z8 = z2^2^2 */ +/* asm 1: fe_sq.fe_sq(>z8=fe#2,z8=fe#2,>z8=fe#2); */ +/* asm 2: fe_sq.fe_sq(>z8=t1,z8=t1,>z8=t1); */ + fe_sq.fe_sq(t1, t0); + for (i = 1; i < 2; ++i) fe_sq.fe_sq(t1, t1); + +/* qhasm: z9 = z1*z8 */ +/* asm 1: fe_mul.fe_mul(>z9=fe#2,z9=t1,z11=fe#1,z11=t0,z22=fe#3,z22=fe#3,>z22=fe#3); */ +/* asm 2: fe_sq.fe_sq(>z22=t2,z22=t2,>z22=t2); */ + fe_sq.fe_sq(t2, t0); + for (i = 1; i < 1; ++i) fe_sq.fe_sq(t2, t2); + +/* qhasm: z_5_0 = z9*z22 */ +/* asm 1: fe_mul.fe_mul(>z_5_0=fe#2,z_5_0=t1,z_10_5=fe#3,z_10_5=fe#3,>z_10_5=fe#3); */ +/* asm 2: fe_sq.fe_sq(>z_10_5=t2,z_10_5=t2,>z_10_5=t2); */ + fe_sq.fe_sq(t2, t1); + for (i = 1; i < 5; ++i) fe_sq.fe_sq(t2, t2); + +/* qhasm: z_10_0 = z_10_5*z_5_0 */ +/* asm 1: fe_mul.fe_mul(>z_10_0=fe#2,z_10_0=t1,z_20_10=fe#3,z_20_10=fe#3,>z_20_10=fe#3); */ +/* asm 2: fe_sq.fe_sq(>z_20_10=t2,z_20_10=t2,>z_20_10=t2); */ + fe_sq.fe_sq(t2, t1); + for (i = 1; i < 10; ++i) fe_sq.fe_sq(t2, t2); + +/* qhasm: z_20_0 = z_20_10*z_10_0 */ +/* asm 1: fe_mul.fe_mul(>z_20_0=fe#3,z_20_0=t2,z_40_20=fe#4,z_40_20=fe#4,>z_40_20=fe#4); */ +/* asm 2: fe_sq.fe_sq(>z_40_20=t3,z_40_20=t3,>z_40_20=t3); */ + fe_sq.fe_sq(t3, t2); + for (i = 1; i < 20; ++i) fe_sq.fe_sq(t3, t3); + +/* qhasm: z_40_0 = z_40_20*z_20_0 */ +/* asm 1: fe_mul.fe_mul(>z_40_0=fe#3,z_40_0=t2,z_50_10=fe#3,z_50_10=fe#3,>z_50_10=fe#3); */ +/* asm 2: fe_sq.fe_sq(>z_50_10=t2,z_50_10=t2,>z_50_10=t2); */ + fe_sq.fe_sq(t2, t2); + for (i = 1; i < 10; ++i) fe_sq.fe_sq(t2, t2); + +/* qhasm: z_50_0 = z_50_10*z_10_0 */ +/* asm 1: fe_mul.fe_mul(>z_50_0=fe#2,z_50_0=t1,z_100_50=fe#3,z_100_50=fe#3,>z_100_50=fe#3); */ +/* asm 2: fe_sq.fe_sq(>z_100_50=t2,z_100_50=t2,>z_100_50=t2); */ + fe_sq.fe_sq(t2, t1); + for (i = 1; i < 50; ++i) fe_sq.fe_sq(t2, t2); + +/* qhasm: z_100_0 = z_100_50*z_50_0 */ +/* asm 1: fe_mul.fe_mul(>z_100_0=fe#3,z_100_0=t2,z_200_100=fe#4,z_200_100=fe#4,>z_200_100=fe#4); */ +/* asm 2: fe_sq.fe_sq(>z_200_100=t3,z_200_100=t3,>z_200_100=t3); */ + fe_sq.fe_sq(t3, t2); + for (i = 1; i < 100; ++i) fe_sq.fe_sq(t3, t3); + +/* qhasm: z_200_0 = z_200_100*z_100_0 */ +/* asm 1: fe_mul.fe_mul(>z_200_0=fe#3,z_200_0=t2,z_250_50=fe#3,z_250_50=fe#3,>z_250_50=fe#3); */ +/* asm 2: fe_sq.fe_sq(>z_250_50=t2,z_250_50=t2,>z_250_50=t2); */ + fe_sq.fe_sq(t2, t2); + for (i = 1; i < 50; ++i) fe_sq.fe_sq(t2, t2); + +/* qhasm: z_250_0 = z_250_50*z_50_0 */ +/* asm 1: fe_mul.fe_mul(>z_250_0=fe#2,z_250_0=t1,z_255_5=fe#2,z_255_5=fe#2,>z_255_5=fe#2); */ +/* asm 2: fe_sq.fe_sq(>z_255_5=t1,z_255_5=t1,>z_255_5=t1); */ + fe_sq.fe_sq(t1, t1); + for (i = 1; i < 5; ++i) fe_sq.fe_sq(t1, t1); + +/* qhasm: z_255_21 = z_255_5*z11 */ +/* asm 1: fe_mul.fe_mul(>z_255_21=fe#12,z_255_21=out,> 26; + hr[1] += carry0; + hr[0] -= carry0 << 26; + carry4 = (hr[4] + (long) (1 << 25)) >> 26; + hr[5] += carry4; + hr[4] -= carry4 << 26; + /* |h0| <= 2^25 */ + /* |h4| <= 2^25 */ + /* |h1| <= 1.71*2^59 */ + /* |h5| <= 1.71*2^59 */ + + carry1 = (hr[1] + (long) (1 << 24)) >> 25; + hr[2] += carry1; + hr[1] -= carry1 << 25; + carry5 = (hr[5] + (long) (1 << 24)) >> 25; + hr[6] += carry5; + hr[5] -= carry5 << 25; + /* |h1| <= 2^24; from now on fits into int32 */ + /* |h5| <= 2^24; from now on fits into int32 */ + /* |h2| <= 1.41*2^60 */ + /* |h6| <= 1.41*2^60 */ + + carry2 = (hr[2] + (long) (1 << 25)) >> 26; + hr[3] += carry2; + hr[2] -= carry2 << 26; + carry6 = (hr[6] + (long) (1 << 25)) >> 26; + hr[7] += carry6; + hr[6] -= carry6 << 26; + /* |h2| <= 2^25; from now on fits into int32 unchanged */ + /* |h6| <= 2^25; from now on fits into int32 unchanged */ + /* |h3| <= 1.71*2^59 */ + /* |h7| <= 1.71*2^59 */ + + carry3 = (hr[3] + (long) (1 << 24)) >> 25; + hr[4] += carry3; + hr[3] -= carry3 << 25; + carry7 = (hr[7] + (long) (1 << 24)) >> 25; + hr[8] += carry7; + hr[7] -= carry7 << 25; + /* |h3| <= 2^24; from now on fits into int32 unchanged */ + /* |h7| <= 2^24; from now on fits into int32 unchanged */ + /* |h4| <= 1.72*2^34 */ + /* |h8| <= 1.41*2^60 */ + + carry4 = (hr[4] + (long) (1 << 25)) >> 26; + hr[5] += carry4; + hr[4] -= carry4 << 26; + carry8 = (hr[8] + (long) (1 << 25)) >> 26; + hr[9] += carry8; + hr[8] -= carry8 << 26; + /* |h4| <= 2^25; from now on fits into int32 unchanged */ + /* |h8| <= 2^25; from now on fits into int32 unchanged */ + /* |h5| <= 1.01*2^24 */ + /* |h9| <= 1.71*2^59 */ + + carry9 = (hr[9] + (long) (1 << 24)) >> 25; + hr[0] += carry9 * 19; + hr[9] -= carry9 << 25; + /* |h9| <= 2^24; from now on fits into int32 unchanged */ + /* |h0| <= 1.1*2^39 */ + + carry0 = (hr[0] + (long) (1 << 25)) >> 26; + hr[1] += carry0; + hr[0] -= carry0 << 26; + /* |h0| <= 2^25; from now on fits into int32 unchanged */ + /* |h1| <= 1.01*2^24 */ + + h[0] = (int) hr[0]; + h[1] = (int) hr[1]; + h[2] = (int) hr[2]; + h[3] = (int) hr[3]; + h[4] = (int) hr[4]; + h[5] = (int) hr[5]; + h[6] = (int) hr[6]; + h[7] = (int) hr[7]; + h[8] = (int) hr[8]; + h[9] = (int) hr[9]; + } +} diff --git a/actor-keygen/src/main/java/im/actor/keygen/curve25519/fe_mul121666.java b/actor-keygen/src/main/java/im/actor/keygen/curve25519/fe_mul121666.java new file mode 100755 index 0000000000..18a83baa3b --- /dev/null +++ b/actor-keygen/src/main/java/im/actor/keygen/curve25519/fe_mul121666.java @@ -0,0 +1,102 @@ +package im.actor.keygen.curve25519; + +// Disabling Bounds checks for speeding up calculations + +/*-[ +#define J2OBJC_DISABLE_ARRAY_BOUND_CHECKS 1 +]-*/ + +public class fe_mul121666 { + +//CONVERT #include "fe.h" +//CONVERT #include "long.h" + +/* +h = f * 121666 +Can overlap h with f. + +Preconditions: + |f| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc. + +Postconditions: + |h| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. +*/ + + public static void fe_mul121666(int[] h, int[] f) { + int f0 = f[0]; + int f1 = f[1]; + int f2 = f[2]; + int f3 = f[3]; + int f4 = f[4]; + int f5 = f[5]; + int f6 = f[6]; + int f7 = f[7]; + int f8 = f[8]; + int f9 = f[9]; + long h0 = f0 * (long) 121666; + long h1 = f1 * (long) 121666; + long h2 = f2 * (long) 121666; + long h3 = f3 * (long) 121666; + long h4 = f4 * (long) 121666; + long h5 = f5 * (long) 121666; + long h6 = f6 * (long) 121666; + long h7 = f7 * (long) 121666; + long h8 = f8 * (long) 121666; + long h9 = f9 * (long) 121666; + long carry0; + long carry1; + long carry2; + long carry3; + long carry4; + long carry5; + long carry6; + long carry7; + long carry8; + long carry9; + + carry9 = (h9 + (long) (1 << 24)) >> 25; + h0 += carry9 * 19; + h9 -= carry9 << 25; + carry1 = (h1 + (long) (1 << 24)) >> 25; + h2 += carry1; + h1 -= carry1 << 25; + carry3 = (h3 + (long) (1 << 24)) >> 25; + h4 += carry3; + h3 -= carry3 << 25; + carry5 = (h5 + (long) (1 << 24)) >> 25; + h6 += carry5; + h5 -= carry5 << 25; + carry7 = (h7 + (long) (1 << 24)) >> 25; + h8 += carry7; + h7 -= carry7 << 25; + + carry0 = (h0 + (long) (1 << 25)) >> 26; + h1 += carry0; + h0 -= carry0 << 26; + carry2 = (h2 + (long) (1 << 25)) >> 26; + h3 += carry2; + h2 -= carry2 << 26; + carry4 = (h4 + (long) (1 << 25)) >> 26; + h5 += carry4; + h4 -= carry4 << 26; + carry6 = (h6 + (long) (1 << 25)) >> 26; + h7 += carry6; + h6 -= carry6 << 26; + carry8 = (h8 + (long) (1 << 25)) >> 26; + h9 += carry8; + h8 -= carry8 << 26; + + h[0] = (int) h0; + h[1] = (int) h1; + h[2] = (int) h2; + h[3] = (int) h3; + h[4] = (int) h4; + h[5] = (int) h5; + h[6] = (int) h6; + h[7] = (int) h7; + h[8] = (int) h8; + h[9] = (int) h9; + } + + +} diff --git a/actor-keygen/src/main/java/im/actor/keygen/curve25519/fe_neg.java b/actor-keygen/src/main/java/im/actor/keygen/curve25519/fe_neg.java new file mode 100755 index 0000000000..c9a3b92a88 --- /dev/null +++ b/actor-keygen/src/main/java/im/actor/keygen/curve25519/fe_neg.java @@ -0,0 +1,57 @@ +package im.actor.keygen.curve25519; + +// Disabling Bounds checks for speeding up calculations + +/*-[ +#define J2OBJC_DISABLE_ARRAY_BOUND_CHECKS 1 +]-*/ + +public class fe_neg { + +//CONVERT #include "fe.h" + +/* +h = -f + +Preconditions: + |f| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. + +Postconditions: + |h| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. +*/ + + public static void fe_neg(int[] h, int[] f) { + int f0 = f[0]; + int f1 = f[1]; + int f2 = f[2]; + int f3 = f[3]; + int f4 = f[4]; + int f5 = f[5]; + int f6 = f[6]; + int f7 = f[7]; + int f8 = f[8]; + int f9 = f[9]; + int h0 = -f0; + int h1 = -f1; + int h2 = -f2; + int h3 = -f3; + int h4 = -f4; + int h5 = -f5; + int h6 = -f6; + int h7 = -f7; + int h8 = -f8; + int h9 = -f9; + h[0] = (int) h0; + h[1] = (int) h1; + h[2] = (int) h2; + h[3] = (int) h3; + h[4] = (int) h4; + h[5] = (int) h5; + h[6] = (int) h6; + h[7] = (int) h7; + h[8] = (int) h8; + h[9] = (int) h9; + } + + +} diff --git a/actor-keygen/src/main/java/im/actor/keygen/curve25519/fe_pow22523.java b/actor-keygen/src/main/java/im/actor/keygen/curve25519/fe_pow22523.java new file mode 100755 index 0000000000..2d580c4cd3 --- /dev/null +++ b/actor-keygen/src/main/java/im/actor/keygen/curve25519/fe_pow22523.java @@ -0,0 +1,196 @@ +package im.actor.keygen.curve25519; + +// Disabling Bounds checks for speeding up calculations + +/*-[ +#define J2OBJC_DISABLE_ARRAY_BOUND_CHECKS 1 +]-*/ + +public class fe_pow22523 { + +//CONVERT #include "fe.h" + + public static void fe_pow22523(int[] out, int[] z) { + int[] t0 = new int[10]; + int[] t1 = new int[10]; + int[] t2 = new int[10]; + int i; + +//CONVERT #include "pow22523.h" + +/* qhasm: fe z1 */ + +/* qhasm: fe z2 */ + +/* qhasm: fe z8 */ + +/* qhasm: fe z9 */ + +/* qhasm: fe z11 */ + +/* qhasm: fe z22 */ + +/* qhasm: fe z_5_0 */ + +/* qhasm: fe z_10_5 */ + +/* qhasm: fe z_10_0 */ + +/* qhasm: fe z_20_10 */ + +/* qhasm: fe z_20_0 */ + +/* qhasm: fe z_40_20 */ + +/* qhasm: fe z_40_0 */ + +/* qhasm: fe z_50_10 */ + +/* qhasm: fe z_50_0 */ + +/* qhasm: fe z_100_50 */ + +/* qhasm: fe z_100_0 */ + +/* qhasm: fe z_200_100 */ + +/* qhasm: fe z_200_0 */ + +/* qhasm: fe z_250_50 */ + +/* qhasm: fe z_250_0 */ + +/* qhasm: fe z_252_2 */ + +/* qhasm: fe z_252_3 */ + +/* qhasm: enter pow22523 */ + +/* qhasm: z2 = z1^2^1 */ +/* asm 1: fe_sq.fe_sq(>z2=fe#1,z2=fe#1,>z2=fe#1); */ +/* asm 2: fe_sq.fe_sq(>z2=t0,z2=t0,>z2=t0); */ + fe_sq.fe_sq(t0, z); + for (i = 1; i < 1; ++i) fe_sq.fe_sq(t0, t0); + +/* qhasm: z8 = z2^2^2 */ +/* asm 1: fe_sq.fe_sq(>z8=fe#2,z8=fe#2,>z8=fe#2); */ +/* asm 2: fe_sq.fe_sq(>z8=t1,z8=t1,>z8=t1); */ + fe_sq.fe_sq(t1, t0); + for (i = 1; i < 2; ++i) fe_sq.fe_sq(t1, t1); + +/* qhasm: z9 = z1*z8 */ +/* asm 1: fe_mul.fe_mul(>z9=fe#2,z9=t1,z11=fe#1,z11=t0,z22=fe#1,z22=fe#1,>z22=fe#1); */ +/* asm 2: fe_sq.fe_sq(>z22=t0,z22=t0,>z22=t0); */ + fe_sq.fe_sq(t0, t0); + for (i = 1; i < 1; ++i) fe_sq.fe_sq(t0, t0); + +/* qhasm: z_5_0 = z9*z22 */ +/* asm 1: fe_mul.fe_mul(>z_5_0=fe#1,z_5_0=t0,z_10_5=fe#2,z_10_5=fe#2,>z_10_5=fe#2); */ +/* asm 2: fe_sq.fe_sq(>z_10_5=t1,z_10_5=t1,>z_10_5=t1); */ + fe_sq.fe_sq(t1, t0); + for (i = 1; i < 5; ++i) fe_sq.fe_sq(t1, t1); + +/* qhasm: z_10_0 = z_10_5*z_5_0 */ +/* asm 1: fe_mul.fe_mul(>z_10_0=fe#1,z_10_0=t0,z_20_10=fe#2,z_20_10=fe#2,>z_20_10=fe#2); */ +/* asm 2: fe_sq.fe_sq(>z_20_10=t1,z_20_10=t1,>z_20_10=t1); */ + fe_sq.fe_sq(t1, t0); + for (i = 1; i < 10; ++i) fe_sq.fe_sq(t1, t1); + +/* qhasm: z_20_0 = z_20_10*z_10_0 */ +/* asm 1: fe_mul.fe_mul(>z_20_0=fe#2,z_20_0=t1,z_40_20=fe#3,z_40_20=fe#3,>z_40_20=fe#3); */ +/* asm 2: fe_sq.fe_sq(>z_40_20=t2,z_40_20=t2,>z_40_20=t2); */ + fe_sq.fe_sq(t2, t1); + for (i = 1; i < 20; ++i) fe_sq.fe_sq(t2, t2); + +/* qhasm: z_40_0 = z_40_20*z_20_0 */ +/* asm 1: fe_mul.fe_mul(>z_40_0=fe#2,z_40_0=t1,z_50_10=fe#2,z_50_10=fe#2,>z_50_10=fe#2); */ +/* asm 2: fe_sq.fe_sq(>z_50_10=t1,z_50_10=t1,>z_50_10=t1); */ + fe_sq.fe_sq(t1, t1); + for (i = 1; i < 10; ++i) fe_sq.fe_sq(t1, t1); + +/* qhasm: z_50_0 = z_50_10*z_10_0 */ +/* asm 1: fe_mul.fe_mul(>z_50_0=fe#1,z_50_0=t0,z_100_50=fe#2,z_100_50=fe#2,>z_100_50=fe#2); */ +/* asm 2: fe_sq.fe_sq(>z_100_50=t1,z_100_50=t1,>z_100_50=t1); */ + fe_sq.fe_sq(t1, t0); + for (i = 1; i < 50; ++i) fe_sq.fe_sq(t1, t1); + +/* qhasm: z_100_0 = z_100_50*z_50_0 */ +/* asm 1: fe_mul.fe_mul(>z_100_0=fe#2,z_100_0=t1,z_200_100=fe#3,z_200_100=fe#3,>z_200_100=fe#3); */ +/* asm 2: fe_sq.fe_sq(>z_200_100=t2,z_200_100=t2,>z_200_100=t2); */ + fe_sq.fe_sq(t2, t1); + for (i = 1; i < 100; ++i) fe_sq.fe_sq(t2, t2); + +/* qhasm: z_200_0 = z_200_100*z_100_0 */ +/* asm 1: fe_mul.fe_mul(>z_200_0=fe#2,z_200_0=t1,z_250_50=fe#2,z_250_50=fe#2,>z_250_50=fe#2); */ +/* asm 2: fe_sq.fe_sq(>z_250_50=t1,z_250_50=t1,>z_250_50=t1); */ + fe_sq.fe_sq(t1, t1); + for (i = 1; i < 50; ++i) fe_sq.fe_sq(t1, t1); + +/* qhasm: z_250_0 = z_250_50*z_50_0 */ +/* asm 1: fe_mul.fe_mul(>z_250_0=fe#1,z_250_0=t0,z_252_2=fe#1,z_252_2=fe#1,>z_252_2=fe#1); */ +/* asm 2: fe_sq.fe_sq(>z_252_2=t0,z_252_2=t0,>z_252_2=t0); */ + fe_sq.fe_sq(t0, t0); + for (i = 1; i < 2; ++i) fe_sq.fe_sq(t0, t0); + +/* qhasm: z_252_3 = z_252_2*z1 */ +/* asm 1: fe_mul.fe_mul(>z_252_3=fe#12,z_252_3=out,> 26; + h1 += carry0; + h0 -= carry0 << 26; + carry4 = (h4 + (long) (1 << 25)) >> 26; + h5 += carry4; + h4 -= carry4 << 26; + + carry1 = (h1 + (long) (1 << 24)) >> 25; + h2 += carry1; + h1 -= carry1 << 25; + carry5 = (h5 + (long) (1 << 24)) >> 25; + h6 += carry5; + h5 -= carry5 << 25; + + carry2 = (h2 + (long) (1 << 25)) >> 26; + h3 += carry2; + h2 -= carry2 << 26; + carry6 = (h6 + (long) (1 << 25)) >> 26; + h7 += carry6; + h6 -= carry6 << 26; + + carry3 = (h3 + (long) (1 << 24)) >> 25; + h4 += carry3; + h3 -= carry3 << 25; + carry7 = (h7 + (long) (1 << 24)) >> 25; + h8 += carry7; + h7 -= carry7 << 25; + + carry4 = (h4 + (long) (1 << 25)) >> 26; + h5 += carry4; + h4 -= carry4 << 26; + carry8 = (h8 + (long) (1 << 25)) >> 26; + h9 += carry8; + h8 -= carry8 << 26; + + carry9 = (h9 + (long) (1 << 24)) >> 25; + h0 += carry9 * 19; + h9 -= carry9 << 25; + + carry0 = (h0 + (long) (1 << 25)) >> 26; + h1 += carry0; + h0 -= carry0 << 26; + + h[0] = (int) h0; + h[1] = (int) h1; + h[2] = (int) h2; + h[3] = (int) h3; + h[4] = (int) h4; + h[5] = (int) h5; + h[6] = (int) h6; + h[7] = (int) h7; + h[8] = (int) h8; + h[9] = (int) h9; + } + + +} diff --git a/actor-keygen/src/main/java/im/actor/keygen/curve25519/fe_sq2.java b/actor-keygen/src/main/java/im/actor/keygen/curve25519/fe_sq2.java new file mode 100755 index 0000000000..0bf6f2a793 --- /dev/null +++ b/actor-keygen/src/main/java/im/actor/keygen/curve25519/fe_sq2.java @@ -0,0 +1,196 @@ +package im.actor.keygen.curve25519; + +// Disabling Bounds checks for speeding up calculations + +/*-[ +#define J2OBJC_DISABLE_ARRAY_BOUND_CHECKS 1 +]-*/ + +public class fe_sq2 { + +//CONVERT #include "fe.h" +//CONVERT #include "long.h" + +/* +h = 2 * f * f +Can overlap h with f. + +Preconditions: + |f| bounded by 1.65*2^26,1.65*2^25,1.65*2^26,1.65*2^25,etc. + +Postconditions: + |h| bounded by 1.01*2^25,1.01*2^24,1.01*2^25,1.01*2^24,etc. +*/ + +/* +See fe_mul.c for discussion of implementation strategy. +*/ + + public static void fe_sq2(int[] h, int[] f) { + int f0 = f[0]; + int f1 = f[1]; + int f2 = f[2]; + int f3 = f[3]; + int f4 = f[4]; + int f5 = f[5]; + int f6 = f[6]; + int f7 = f[7]; + int f8 = f[8]; + int f9 = f[9]; + int f0_2 = 2 * f0; + int f1_2 = 2 * f1; + int f2_2 = 2 * f2; + int f3_2 = 2 * f3; + int f4_2 = 2 * f4; + int f5_2 = 2 * f5; + int f6_2 = 2 * f6; + int f7_2 = 2 * f7; + int f5_38 = 38 * f5; /* 1.959375*2^30 */ + int f6_19 = 19 * f6; /* 1.959375*2^30 */ + int f7_38 = 38 * f7; /* 1.959375*2^30 */ + int f8_19 = 19 * f8; /* 1.959375*2^30 */ + int f9_38 = 38 * f9; /* 1.959375*2^30 */ + long f0f0 = f0 * (long) f0; + long f0f1_2 = f0_2 * (long) f1; + long f0f2_2 = f0_2 * (long) f2; + long f0f3_2 = f0_2 * (long) f3; + long f0f4_2 = f0_2 * (long) f4; + long f0f5_2 = f0_2 * (long) f5; + long f0f6_2 = f0_2 * (long) f6; + long f0f7_2 = f0_2 * (long) f7; + long f0f8_2 = f0_2 * (long) f8; + long f0f9_2 = f0_2 * (long) f9; + long f1f1_2 = f1_2 * (long) f1; + long f1f2_2 = f1_2 * (long) f2; + long f1f3_4 = f1_2 * (long) f3_2; + long f1f4_2 = f1_2 * (long) f4; + long f1f5_4 = f1_2 * (long) f5_2; + long f1f6_2 = f1_2 * (long) f6; + long f1f7_4 = f1_2 * (long) f7_2; + long f1f8_2 = f1_2 * (long) f8; + long f1f9_76 = f1_2 * (long) f9_38; + long f2f2 = f2 * (long) f2; + long f2f3_2 = f2_2 * (long) f3; + long f2f4_2 = f2_2 * (long) f4; + long f2f5_2 = f2_2 * (long) f5; + long f2f6_2 = f2_2 * (long) f6; + long f2f7_2 = f2_2 * (long) f7; + long f2f8_38 = f2_2 * (long) f8_19; + long f2f9_38 = f2 * (long) f9_38; + long f3f3_2 = f3_2 * (long) f3; + long f3f4_2 = f3_2 * (long) f4; + long f3f5_4 = f3_2 * (long) f5_2; + long f3f6_2 = f3_2 * (long) f6; + long f3f7_76 = f3_2 * (long) f7_38; + long f3f8_38 = f3_2 * (long) f8_19; + long f3f9_76 = f3_2 * (long) f9_38; + long f4f4 = f4 * (long) f4; + long f4f5_2 = f4_2 * (long) f5; + long f4f6_38 = f4_2 * (long) f6_19; + long f4f7_38 = f4 * (long) f7_38; + long f4f8_38 = f4_2 * (long) f8_19; + long f4f9_38 = f4 * (long) f9_38; + long f5f5_38 = f5 * (long) f5_38; + long f5f6_38 = f5_2 * (long) f6_19; + long f5f7_76 = f5_2 * (long) f7_38; + long f5f8_38 = f5_2 * (long) f8_19; + long f5f9_76 = f5_2 * (long) f9_38; + long f6f6_19 = f6 * (long) f6_19; + long f6f7_38 = f6 * (long) f7_38; + long f6f8_38 = f6_2 * (long) f8_19; + long f6f9_38 = f6 * (long) f9_38; + long f7f7_38 = f7 * (long) f7_38; + long f7f8_38 = f7_2 * (long) f8_19; + long f7f9_76 = f7_2 * (long) f9_38; + long f8f8_19 = f8 * (long) f8_19; + long f8f9_38 = f8 * (long) f9_38; + long f9f9_38 = f9 * (long) f9_38; + long h0 = f0f0 + f1f9_76 + f2f8_38 + f3f7_76 + f4f6_38 + f5f5_38; + long h1 = f0f1_2 + f2f9_38 + f3f8_38 + f4f7_38 + f5f6_38; + long h2 = f0f2_2 + f1f1_2 + f3f9_76 + f4f8_38 + f5f7_76 + f6f6_19; + long h3 = f0f3_2 + f1f2_2 + f4f9_38 + f5f8_38 + f6f7_38; + long h4 = f0f4_2 + f1f3_4 + f2f2 + f5f9_76 + f6f8_38 + f7f7_38; + long h5 = f0f5_2 + f1f4_2 + f2f3_2 + f6f9_38 + f7f8_38; + long h6 = f0f6_2 + f1f5_4 + f2f4_2 + f3f3_2 + f7f9_76 + f8f8_19; + long h7 = f0f7_2 + f1f6_2 + f2f5_2 + f3f4_2 + f8f9_38; + long h8 = f0f8_2 + f1f7_4 + f2f6_2 + f3f5_4 + f4f4 + f9f9_38; + long h9 = f0f9_2 + f1f8_2 + f2f7_2 + f3f6_2 + f4f5_2; + long carry0; + long carry1; + long carry2; + long carry3; + long carry4; + long carry5; + long carry6; + long carry7; + long carry8; + long carry9; + + h0 += h0; + h1 += h1; + h2 += h2; + h3 += h3; + h4 += h4; + h5 += h5; + h6 += h6; + h7 += h7; + h8 += h8; + h9 += h9; + + carry0 = (h0 + (long) (1 << 25)) >> 26; + h1 += carry0; + h0 -= carry0 << 26; + carry4 = (h4 + (long) (1 << 25)) >> 26; + h5 += carry4; + h4 -= carry4 << 26; + + carry1 = (h1 + (long) (1 << 24)) >> 25; + h2 += carry1; + h1 -= carry1 << 25; + carry5 = (h5 + (long) (1 << 24)) >> 25; + h6 += carry5; + h5 -= carry5 << 25; + + carry2 = (h2 + (long) (1 << 25)) >> 26; + h3 += carry2; + h2 -= carry2 << 26; + carry6 = (h6 + (long) (1 << 25)) >> 26; + h7 += carry6; + h6 -= carry6 << 26; + + carry3 = (h3 + (long) (1 << 24)) >> 25; + h4 += carry3; + h3 -= carry3 << 25; + carry7 = (h7 + (long) (1 << 24)) >> 25; + h8 += carry7; + h7 -= carry7 << 25; + + carry4 = (h4 + (long) (1 << 25)) >> 26; + h5 += carry4; + h4 -= carry4 << 26; + carry8 = (h8 + (long) (1 << 25)) >> 26; + h9 += carry8; + h8 -= carry8 << 26; + + carry9 = (h9 + (long) (1 << 24)) >> 25; + h0 += carry9 * 19; + h9 -= carry9 << 25; + + carry0 = (h0 + (long) (1 << 25)) >> 26; + h1 += carry0; + h0 -= carry0 << 26; + + h[0] = (int) h0; + h[1] = (int) h1; + h[2] = (int) h2; + h[3] = (int) h3; + h[4] = (int) h4; + h[5] = (int) h5; + h[6] = (int) h6; + h[7] = (int) h7; + h[8] = (int) h8; + h[9] = (int) h9; + } + + +} diff --git a/actor-keygen/src/main/java/im/actor/keygen/curve25519/fe_sub.java b/actor-keygen/src/main/java/im/actor/keygen/curve25519/fe_sub.java new file mode 100755 index 0000000000..bd142607d5 --- /dev/null +++ b/actor-keygen/src/main/java/im/actor/keygen/curve25519/fe_sub.java @@ -0,0 +1,69 @@ +package im.actor.keygen.curve25519; + +// Disabling Bounds checks for speeding up calculations + +/*-[ +#define J2OBJC_DISABLE_ARRAY_BOUND_CHECKS 1 +]-*/ + +public class fe_sub { + +//CONVERT #include "fe.h" + +/* +h = f - g +Can overlap h with f or g. + +Preconditions: + |f| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. + |g| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. + +Postconditions: + |h| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc. +*/ + + public static void fe_sub(int[] h, int[] f, int[] g) { + int f0 = f[0]; + int f1 = f[1]; + int f2 = f[2]; + int f3 = f[3]; + int f4 = f[4]; + int f5 = f[5]; + int f6 = f[6]; + int f7 = f[7]; + int f8 = f[8]; + int f9 = f[9]; + int g0 = g[0]; + int g1 = g[1]; + int g2 = g[2]; + int g3 = g[3]; + int g4 = g[4]; + int g5 = g[5]; + int g6 = g[6]; + int g7 = g[7]; + int g8 = g[8]; + int g9 = g[9]; + int h0 = f0 - g0; + int h1 = f1 - g1; + int h2 = f2 - g2; + int h3 = f3 - g3; + int h4 = f4 - g4; + int h5 = f5 - g5; + int h6 = f6 - g6; + int h7 = f7 - g7; + int h8 = f8 - g8; + int h9 = f9 - g9; + h[0] = (int) h0; + h[1] = (int) h1; + h[2] = (int) h2; + h[3] = (int) h3; + h[4] = (int) h4; + h[5] = (int) h5; + h[6] = (int) h6; + h[7] = (int) h7; + h[8] = (int) h8; + h[9] = (int) h9; + } + + +} diff --git a/actor-keygen/src/main/java/im/actor/keygen/curve25519/fe_tobytes.java b/actor-keygen/src/main/java/im/actor/keygen/curve25519/fe_tobytes.java new file mode 100755 index 0000000000..52ac449393 --- /dev/null +++ b/actor-keygen/src/main/java/im/actor/keygen/curve25519/fe_tobytes.java @@ -0,0 +1,150 @@ +package im.actor.keygen.curve25519; + +// Disabling Bounds checks for speeding up calculations + +/*-[ +#define J2OBJC_DISABLE_ARRAY_BOUND_CHECKS 1 +]-*/ + +public class fe_tobytes { + +//CONVERT #include "fe.h" + +/* +Preconditions: + |h| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc. + +Write p=2^255-19; q=floor(h/p). +Basic claim: q = floor(2^(-255)(h + 19 2^(-25)h9 + 2^(-1))). + +Proof: + Have |h|<=p so |q|<=1 so |19^2 2^(-255) q|<1/4. + Also have |h-2^230 h9|<2^231 so |19 2^(-255)(h-2^230 h9)|<1/4. + + Write y=2^(-1)-19^2 2^(-255)q-19 2^(-255)(h-2^230 h9). + Then 0> 25; + q = (h0 + q) >> 26; + q = (h1 + q) >> 25; + q = (h2 + q) >> 26; + q = (h3 + q) >> 25; + q = (h4 + q) >> 26; + q = (h5 + q) >> 25; + q = (h6 + q) >> 26; + q = (h7 + q) >> 25; + q = (h8 + q) >> 26; + q = (h9 + q) >> 25; + + /* Goal: Output h-(2^255-19)q, which is between 0 and 2^255-20. */ + h0 += 19 * q; + /* Goal: Output h-2^255 q, which is between 0 and 2^255-20. */ + + carry0 = h0 >> 26; + h1 += carry0; + h0 -= carry0 << 26; + carry1 = h1 >> 25; + h2 += carry1; + h1 -= carry1 << 25; + carry2 = h2 >> 26; + h3 += carry2; + h2 -= carry2 << 26; + carry3 = h3 >> 25; + h4 += carry3; + h3 -= carry3 << 25; + carry4 = h4 >> 26; + h5 += carry4; + h4 -= carry4 << 26; + carry5 = h5 >> 25; + h6 += carry5; + h5 -= carry5 << 25; + carry6 = h6 >> 26; + h7 += carry6; + h6 -= carry6 << 26; + carry7 = h7 >> 25; + h8 += carry7; + h7 -= carry7 << 25; + carry8 = h8 >> 26; + h9 += carry8; + h8 -= carry8 << 26; + carry9 = h9 >> 25; + h9 -= carry9 << 25; + /* h10 = carry9 */ + + /* + Goal: Output h0+...+2^255 h10-2^255 q, which is between 0 and 2^255-20. + Have h0+...+2^230 h9 between 0 and 2^255-1; + evidently 2^255 h10-2^255 q = 0. + Goal: Output h0+...+2^230 h9. + */ + + s[0] = (byte) (h0 >> 0); + s[1] = (byte) (h0 >> 8); + s[2] = (byte) (h0 >> 16); + s[3] = (byte) ((h0 >> 24) | (h1 << 2)); + s[4] = (byte) (h1 >> 6); + s[5] = (byte) (h1 >> 14); + s[6] = (byte) ((h1 >> 22) | (h2 << 3)); + s[7] = (byte) (h2 >> 5); + s[8] = (byte) (h2 >> 13); + s[9] = (byte) ((h2 >> 21) | (h3 << 5)); + s[10] = (byte) (h3 >> 3); + s[11] = (byte) (h3 >> 11); + s[12] = (byte) ((h3 >> 19) | (h4 << 6)); + s[13] = (byte) (h4 >> 2); + s[14] = (byte) (h4 >> 10); + s[15] = (byte) (h4 >> 18); + s[16] = (byte) (h5 >> 0); + s[17] = (byte) (h5 >> 8); + s[18] = (byte) (h5 >> 16); + s[19] = (byte) ((h5 >> 24) | (h6 << 1)); + s[20] = (byte) (h6 >> 7); + s[21] = (byte) (h6 >> 15); + s[22] = (byte) ((h6 >> 23) | (h7 << 3)); + s[23] = (byte) (h7 >> 5); + s[24] = (byte) (h7 >> 13); + s[25] = (byte) ((h7 >> 21) | (h8 << 4)); + s[26] = (byte) (h8 >> 4); + s[27] = (byte) (h8 >> 12); + s[28] = (byte) ((h8 >> 20) | (h9 << 6)); + s[29] = (byte) (h9 >> 2); + s[30] = (byte) (h9 >> 10); + s[31] = (byte) (h9 >> 18); + } + + +} diff --git a/actor-keygen/src/main/java/im/actor/keygen/curve25519/ge_add.java b/actor-keygen/src/main/java/im/actor/keygen/curve25519/ge_add.java new file mode 100755 index 0000000000..81cb95a9bb --- /dev/null +++ b/actor-keygen/src/main/java/im/actor/keygen/curve25519/ge_add.java @@ -0,0 +1,120 @@ +package im.actor.keygen.curve25519; + +// Disabling Bounds checks for speeding up calculations + +/*-[ +#define J2OBJC_DISABLE_ARRAY_BOUND_CHECKS 1 +]-*/ + +public class ge_add { + +//CONVERT #include "ge.h" + +/* +r = p + q +*/ + + public static void ge_add(ge_p1p1 r, ge_p3 p, ge_cached q) { + int[] t0 = new int[10]; +//CONVERT #include "ge_add.h" + +/* qhasm: enter ge_add */ + +/* qhasm: fe X1 */ + +/* qhasm: fe Y1 */ + +/* qhasm: fe Z1 */ + +/* qhasm: fe Z2 */ + +/* qhasm: fe T1 */ + +/* qhasm: fe ZZ */ + +/* qhasm: fe YpX2 */ + +/* qhasm: fe YmX2 */ + +/* qhasm: fe T2d2 */ + +/* qhasm: fe X3 */ + +/* qhasm: fe Y3 */ + +/* qhasm: fe Z3 */ + +/* qhasm: fe T3 */ + +/* qhasm: fe YpX1 */ + +/* qhasm: fe YmX1 */ + +/* qhasm: fe A */ + +/* qhasm: fe B */ + +/* qhasm: fe C */ + +/* qhasm: fe D */ + +/* qhasm: YpX1 = Y1+X1 */ +/* asm 1: fe_add.fe_add(>YpX1=fe#1,YpX1=r.X,YmX1=fe#2,YmX1=r.Y,A=fe#3,A=r.Z,B=fe#2,B=r.Y,C=fe#4,C=r.T,ZZ=fe#1,ZZ=r.X,D=fe#5,D=t0,X3=fe#1,X3=r.X,Y3=fe#2,Y3=r.Y,Z3=fe#3,Z3=r.Z,T3=fe#4,T3=r.T,> 3] >> (i & 7)); + r[i] = (byte) (1 & (a[i >> 3] >>> (i & 7))); + } + + for (i = 0; i < 256; ++i) + if (r[i] != 0) { + for (b = 1; b <= 6 && i + b < 256; ++b) { + if (r[i + b] != 0) { + if (r[i] + (r[i + b] << b) <= 15) { + r[i] += r[i + b] << b; + r[i + b] = 0; + } else if (r[i] - (r[i + b] << b) >= -15) { + r[i] -= r[i + b] << b; + for (k = i + b; k < 256; ++k) { + if (r[k] == 0) { + r[k] = 1; + break; + } + r[k] = 0; + } + } else + break; + } + } + } + + } + + static ge_precomp Bi[]; + + static { + Bi = new ge_precomp[8]; + Bi[0] = new ge_precomp( + new int[]{25967493, -14356035, 29566456, 3660896, -12694345, 4014787, 27544626, -11754271, -6079156, 2047605}, + new int[]{-12545711, 934262, -2722910, 3049990, -727428, 9406986, 12720692, 5043384, 19500929, -15469378}, + new int[]{-8738181, 4489570, 9688441, -14785194, 10184609, -12363380, 29287919, 11864899, -24514362, -4438546} + ); + Bi[1] = new ge_precomp( + new int[]{15636291, -9688557, 24204773, -7912398, 616977, -16685262, 27787600, -14772189, 28944400, -1550024}, + new int[]{16568933, 4717097, -11556148, -1102322, 15682896, -11807043, 16354577, -11775962, 7689662, 11199574}, + new int[]{30464156, -5976125, -11779434, -15670865, 23220365, 15915852, 7512774, 10017326, -17749093, -9920357} + ); + Bi[2] = new ge_precomp( + new int[]{10861363, 11473154, 27284546, 1981175, -30064349, 12577861, 32867885, 14515107, -15438304, 10819380}, + new int[]{4708026, 6336745, 20377586, 9066809, -11272109, 6594696, -25653668, 12483688, -12668491, 5581306}, + new int[]{19563160, 16186464, -29386857, 4097519, 10237984, -4348115, 28542350, 13850243, -23678021, -15815942} + ); + Bi[3] = new ge_precomp( + new int[]{5153746, 9909285, 1723747, -2777874, 30523605, 5516873, 19480852, 5230134, -23952439, -15175766}, + new int[]{-30269007, -3463509, 7665486, 10083793, 28475525, 1649722, 20654025, 16520125, 30598449, 7715701}, + new int[]{28881845, 14381568, 9657904, 3680757, -20181635, 7843316, -31400660, 1370708, 29794553, -1409300} + ); + Bi[4] = new ge_precomp( + new int[]{-22518993, -6692182, 14201702, -8745502, -23510406, 8844726, 18474211, -1361450, -13062696, 13821877}, + new int[]{-6455177, -7839871, 3374702, -4740862, -27098617, -10571707, 31655028, -7212327, 18853322, -14220951}, + new int[]{4566830, -12963868, -28974889, -12240689, -7602672, -2830569, -8514358, -10431137, 2207753, -3209784} + ); + Bi[5] = new ge_precomp( + new int[]{-25154831, -4185821, 29681144, 7868801, -6854661, -9423865, -12437364, -663000, -31111463, -16132436}, + new int[]{25576264, -2703214, 7349804, -11814844, 16472782, 9300885, 3844789, 15725684, 171356, 6466918}, + new int[]{23103977, 13316479, 9739013, -16149481, 817875, -15038942, 8965339, -14088058, -30714912, 16193877} + ); + Bi[6] = new ge_precomp( + new int[]{-33521811, 3180713, -2394130, 14003687, -16903474, -16270840, 17238398, 4729455, -18074513, 9256800}, + new int[]{-25182317, -4174131, 32336398, 5036987, -21236817, 11360617, 22616405, 9761698, -19827198, 630305}, + new int[]{-13720693, 2639453, -24237460, -7406481, 9494427, -5774029, -6554551, -15960994, -2449256, -14291300} + ); + Bi[7] = new ge_precomp( + new int[]{-3151181, -5046075, 9282714, 6866145, -31907062, -863023, -18940575, 15033784, 25105118, -7894876}, + new int[]{-24326370, 15950226, -31801215, -14592823, -11662737, -5090925, 1573892, -2625887, 2198790, -15804619}, + new int[]{-3099351, 10324967, -2241613, 7453183, -5446979, -2735503, -13812022, -16236442, -32461234, -12290683} + ); + } + +/* +r = a * A + b * B +where a = a[0]+256*a[1]+...+256^31 a[31]. +and b = b[0]+256*b[1]+...+256^31 b[31]. +B is the Ed25519 base point (x,4/5) with x positive. +*/ + + public static void ge_double_scalarmult_vartime(ge_p2 r, byte[] a, ge_p3 A, byte[] b) { + byte[] aslide = new byte[256]; + byte[] bslide = new byte[256]; + ge_cached Ai[] = new ge_cached[8]; /* A,3A,5A,7A,9A,11A,13A,15A */ + for (int count = 0; count < 8; count++) + Ai[count] = new ge_cached(); + ge_p1p1 t = new ge_p1p1(); + ge_p3 u = new ge_p3(); + ge_p3 A2 = new ge_p3(); + int i; + + slide(aslide, a); + slide(bslide, b); + + ge_p3_to_cached.ge_p3_to_cached(Ai[0], A); + ge_p3_dbl.ge_p3_dbl(t, A); + ge_p1p1_to_p3.ge_p1p1_to_p3(A2, t); + ge_add.ge_add(t, A2, Ai[0]); + ge_p1p1_to_p3.ge_p1p1_to_p3(u, t); + ge_p3_to_cached.ge_p3_to_cached(Ai[1], u); + ge_add.ge_add(t, A2, Ai[1]); + ge_p1p1_to_p3.ge_p1p1_to_p3(u, t); + ge_p3_to_cached.ge_p3_to_cached(Ai[2], u); + ge_add.ge_add(t, A2, Ai[2]); + ge_p1p1_to_p3.ge_p1p1_to_p3(u, t); + ge_p3_to_cached.ge_p3_to_cached(Ai[3], u); + ge_add.ge_add(t, A2, Ai[3]); + ge_p1p1_to_p3.ge_p1p1_to_p3(u, t); + ge_p3_to_cached.ge_p3_to_cached(Ai[4], u); + ge_add.ge_add(t, A2, Ai[4]); + ge_p1p1_to_p3.ge_p1p1_to_p3(u, t); + ge_p3_to_cached.ge_p3_to_cached(Ai[5], u); + ge_add.ge_add(t, A2, Ai[5]); + ge_p1p1_to_p3.ge_p1p1_to_p3(u, t); + ge_p3_to_cached.ge_p3_to_cached(Ai[6], u); + ge_add.ge_add(t, A2, Ai[6]); + ge_p1p1_to_p3.ge_p1p1_to_p3(u, t); + ge_p3_to_cached.ge_p3_to_cached(Ai[7], u); + + ge_p2_0.ge_p2_0(r); + + for (i = 255; i >= 0; --i) { + if (aslide[i] != 0 || bslide[i] != 0) break; + } + + for (; i >= 0; --i) { + ge_p2_dbl.ge_p2_dbl(t, r); + + if (aslide[i] > 0) { + ge_p1p1_to_p3.ge_p1p1_to_p3(u, t); + ge_add.ge_add(t, u, Ai[aslide[i] / 2]); + } else if (aslide[i] < 0) { + ge_p1p1_to_p3.ge_p1p1_to_p3(u, t); + ge_sub.ge_sub(t, u, Ai[(-aslide[i]) / 2]); + } + + if (bslide[i] > 0) { + ge_p1p1_to_p3.ge_p1p1_to_p3(u, t); + ge_madd.ge_madd(t, u, Bi[bslide[i] / 2]); + } else if (bslide[i] < 0) { + ge_p1p1_to_p3.ge_p1p1_to_p3(u, t); + ge_msub.ge_msub(t, u, Bi[(-bslide[i]) / 2]); + } + + ge_p1p1_to_p2.ge_p1p1_to_p2(r, t); + } + } + + +} diff --git a/actor-keygen/src/main/java/im/actor/keygen/curve25519/ge_frombytes.java b/actor-keygen/src/main/java/im/actor/keygen/curve25519/ge_frombytes.java new file mode 100755 index 0000000000..279071419f --- /dev/null +++ b/actor-keygen/src/main/java/im/actor/keygen/curve25519/ge_frombytes.java @@ -0,0 +1,65 @@ +package im.actor.keygen.curve25519; + +// Disabling Bounds checks for speeding up calculations + +/*-[ +#define J2OBJC_DISABLE_ARRAY_BOUND_CHECKS 1 +]-*/ + +public class ge_frombytes { + +//CONVERT #include "ge.h" + + static int[] d = { +//CONVERT #include "d.h" + -10913610, 13857413, -15372611, 6949391, 114729, -8787816, -6275908, -3247719, -18696448, -12055116 + }; + + static int[] sqrtm1 = { +//CONVERT #include "sqrtm1.h" + -32595792, -7943725, 9377950, 3500415, 12389472, -272473, -25146209, -2005654, 326686, 11406482 + }; + + static int ge_frombytes_negate_vartime(ge_p3 h, byte[] s) { + int[] u = new int[10]; + int[] v = new int[10]; + int[] v3 = new int[10]; + int[] vxx = new int[10]; + int[] check = new int[10]; + + fe_frombytes.fe_frombytes(h.Y, s); + fe_1.fe_1(h.Z); + fe_sq.fe_sq(u, h.Y); + fe_mul.fe_mul(v, u, d); + fe_sub.fe_sub(u, u, h.Z); /* u = y^2-1 */ + fe_add.fe_add(v, v, h.Z); /* v = dy^2+1 */ + + fe_sq.fe_sq(v3, v); + fe_mul.fe_mul(v3, v3, v); /* v3 = v^3 */ + fe_sq.fe_sq(h.X, v3); + fe_mul.fe_mul(h.X, h.X, v); + fe_mul.fe_mul(h.X, h.X, u); /* x = uv^7 */ + + fe_pow22523.fe_pow22523(h.X, h.X); /* x = (uv^7)^((q-5)/8) */ + fe_mul.fe_mul(h.X, h.X, v3); + fe_mul.fe_mul(h.X, h.X, u); /* x = uv^3(uv^7)^((q-5)/8) */ + + fe_sq.fe_sq(vxx, h.X); + fe_mul.fe_mul(vxx, vxx, v); + fe_sub.fe_sub(check, vxx, u); /* vx^2-u */ + if (fe_isnonzero.fe_isnonzero(check) != 0) { + fe_add.fe_add(check, vxx, u); /* vx^2+u */ + if (fe_isnonzero.fe_isnonzero(check) != 0) return -1; + fe_mul.fe_mul(h.X, h.X, sqrtm1); + } + + if (fe_isnegative.fe_isnegative(h.X) == ((s[31] >>> 7) & 0x01)) { + fe_neg.fe_neg(h.X, h.X); + } + + fe_mul.fe_mul(h.T, h.X, h.Y); + return 0; + } + + +} diff --git a/actor-keygen/src/main/java/im/actor/keygen/curve25519/ge_madd.java b/actor-keygen/src/main/java/im/actor/keygen/curve25519/ge_madd.java new file mode 100755 index 0000000000..9d2fce9174 --- /dev/null +++ b/actor-keygen/src/main/java/im/actor/keygen/curve25519/ge_madd.java @@ -0,0 +1,111 @@ +package im.actor.keygen.curve25519; + +// Disabling Bounds checks for speeding up calculations + +/*-[ +#define J2OBJC_DISABLE_ARRAY_BOUND_CHECKS 1 +]-*/ + +public class ge_madd { + +//CONVERT #include "ge.h" + +/* +r = p + q +*/ + + public static void ge_madd(ge_p1p1 r, ge_p3 p, ge_precomp q) { + int[] t0 = new int[10]; +//CONVERT #include "ge_madd.h" + +/* qhasm: enter ge_madd */ + +/* qhasm: fe X1 */ + +/* qhasm: fe Y1 */ + +/* qhasm: fe Z1 */ + +/* qhasm: fe T1 */ + +/* qhasm: fe ypx2 */ + +/* qhasm: fe ymx2 */ + +/* qhasm: fe xy2d2 */ + +/* qhasm: fe X3 */ + +/* qhasm: fe Y3 */ + +/* qhasm: fe Z3 */ + +/* qhasm: fe T3 */ + +/* qhasm: fe YpX1 */ + +/* qhasm: fe YmX1 */ + +/* qhasm: fe A */ + +/* qhasm: fe B */ + +/* qhasm: fe C */ + +/* qhasm: fe D */ + +/* qhasm: YpX1 = Y1+X1 */ +/* asm 1: fe_add.fe_add(>YpX1=fe#1,YpX1=r.X,YmX1=fe#2,YmX1=r.Y,A=fe#3,A=r.Z,B=fe#2,B=r.Y,C=fe#4,C=r.T,D=fe#5,D=t0,X3=fe#1,X3=r.X,Y3=fe#2,Y3=r.Y,Z3=fe#3,Z3=r.Z,T3=fe#4,T3=r.T,YpX1=fe#1,YpX1=r.X,YmX1=fe#2,YmX1=r.Y,A=fe#3,A=r.Z,B=fe#2,B=r.Y,C=fe#4,C=r.T,D=fe#5,D=t0,X3=fe#1,X3=r.X,Y3=fe#2,Y3=r.Y,Z3=fe#3,Z3=r.Z,T3=fe#4,T3=r.T,XX=fe#1,XX=r.X,YY=fe#3,YY=r.Z,B=fe#4,B=r.T,A=fe#2,A=r.Y,AA=fe#5,AA=t0,Y3=fe#2,Y3=r.Y,Z3=fe#3,Z3=r.Z,X3=fe#1,X3=r.X,T3=fe#4,T3=r.T,>>= 31; /* 1: yes; 0: no */ + return y; + } + + static int negative(byte b) { + long x = b; /* 18446744073709551361..18446744073709551615: yes; 0..255: no */ + x >>>= 63; /* 1: yes; 0: no */ + return (int) x; + } + + static void cmov(ge_precomp t, ge_precomp u, int b) { + fe_cmov.fe_cmov(t.yplusx, u.yplusx, b); + fe_cmov.fe_cmov(t.yminusx, u.yminusx, b); + fe_cmov.fe_cmov(t.xy2d, u.xy2d, b); + } + + static void select(ge_precomp t, int pos, byte b) { + ge_precomp base[][] = (pos <= 7 ? ge_precomp_base_0_7.base : + (pos <= 15 ? ge_precomp_base_8_15.base : + (pos <= 23 ? ge_precomp_base_16_23.base : ge_precomp_base_24_31.base))); + + ge_precomp minust = new ge_precomp(); + int bnegative = negative(b); + int babs = b - (((-bnegative) & b) << 1); + + ge_precomp_0.ge_precomp_0(t); + cmov(t, base[pos][0], equal((byte) babs, (byte) 1)); + cmov(t, base[pos][1], equal((byte) babs, (byte) 2)); + cmov(t, base[pos][2], equal((byte) babs, (byte) 3)); + cmov(t, base[pos][3], equal((byte) babs, (byte) 4)); + cmov(t, base[pos][4], equal((byte) babs, (byte) 5)); + cmov(t, base[pos][5], equal((byte) babs, (byte) 6)); + cmov(t, base[pos][6], equal((byte) babs, (byte) 7)); + cmov(t, base[pos][7], equal((byte) babs, (byte) 8)); + fe_copy.fe_copy(minust.yplusx, t.yminusx); + fe_copy.fe_copy(minust.yminusx, t.yplusx); + fe_neg.fe_neg(minust.xy2d, t.xy2d); + cmov(t, minust, bnegative); + } + +/* +h = a * B +where a = a[0]+256*a[1]+...+256^31 a[31] +B is the Ed25519 base point (x,4/5) with x positive. + +Preconditions: + a[31] <= 127 +*/ + + public static void ge_scalarmult_base(ge_p3 h, byte[] a) { + byte[] e = new byte[64]; + byte carry; + ge_p1p1 r = new ge_p1p1(); + ge_p2 s = new ge_p2(); + ge_precomp t = new ge_precomp(); + int i; + + for (i = 0; i < 32; ++i) { + e[2 * i + 0] = (byte) ((a[i] >>> 0) & 15); + e[2 * i + 1] = (byte) ((a[i] >>> 4) & 15); + } + /* each e[i] is between 0 and 15 */ + /* e[63] is between 0 and 7 */ + + carry = 0; + for (i = 0; i < 63; ++i) { + e[i] += carry; + carry = (byte) (e[i] + 8); + carry >>= 4; + e[i] -= carry << 4; + } + e[63] += carry; + /* each e[i] is between -8 and 8 */ + + ge_p3_0.ge_p3_0(h); + for (i = 1; i < 64; i += 2) { + select(t, i / 2, e[i]); + ge_madd.ge_madd(r, h, t); + ge_p1p1_to_p3.ge_p1p1_to_p3(h, r); + } + + ge_p3_dbl.ge_p3_dbl(r, h); + ge_p1p1_to_p2.ge_p1p1_to_p2(s, r); + ge_p2_dbl.ge_p2_dbl(r, s); + ge_p1p1_to_p2.ge_p1p1_to_p2(s, r); + ge_p2_dbl.ge_p2_dbl(r, s); + ge_p1p1_to_p2.ge_p1p1_to_p2(s, r); + ge_p2_dbl.ge_p2_dbl(r, s); + ge_p1p1_to_p3.ge_p1p1_to_p3(h, r); + + for (i = 0; i < 64; i += 2) { + select(t, i / 2, e[i]); + ge_madd.ge_madd(r, h, t); + ge_p1p1_to_p3.ge_p1p1_to_p3(h, r); + } + } + +} diff --git a/actor-keygen/src/main/java/im/actor/keygen/curve25519/ge_sub.java b/actor-keygen/src/main/java/im/actor/keygen/curve25519/ge_sub.java new file mode 100755 index 0000000000..274c898f5b --- /dev/null +++ b/actor-keygen/src/main/java/im/actor/keygen/curve25519/ge_sub.java @@ -0,0 +1,120 @@ +package im.actor.keygen.curve25519; + +// Disabling Bounds checks for speeding up calculations + +/*-[ +#define J2OBJC_DISABLE_ARRAY_BOUND_CHECKS 1 +]-*/ + +public class ge_sub { + +//CONVERT #include "ge.h" + +/* +r = p - q +*/ + + public static void ge_sub(ge_p1p1 r, ge_p3 p, ge_cached q) { + int[] t0 = new int[10]; +//CONVERT #include "ge_sub.h" + +/* qhasm: enter ge_sub */ + +/* qhasm: fe X1 */ + +/* qhasm: fe Y1 */ + +/* qhasm: fe Z1 */ + +/* qhasm: fe Z2 */ + +/* qhasm: fe T1 */ + +/* qhasm: fe ZZ */ + +/* qhasm: fe YpX2 */ + +/* qhasm: fe YmX2 */ + +/* qhasm: fe T2d2 */ + +/* qhasm: fe X3 */ + +/* qhasm: fe Y3 */ + +/* qhasm: fe Z3 */ + +/* qhasm: fe T3 */ + +/* qhasm: fe YpX1 */ + +/* qhasm: fe YmX1 */ + +/* qhasm: fe A */ + +/* qhasm: fe B */ + +/* qhasm: fe C */ + +/* qhasm: fe D */ + +/* qhasm: YpX1 = Y1+X1 */ +/* asm 1: fe_add.fe_add(>YpX1=fe#1,YpX1=r.X,YmX1=fe#2,YmX1=r.Y,A=fe#3,A=r.Z,B=fe#2,B=r.Y,C=fe#4,C=r.T,ZZ=fe#1,ZZ=r.X,D=fe#5,D=t0,X3=fe#1,X3=r.X,Y3=fe#2,Y3=r.Y,Z3=fe#3,Z3=r.Z,T3=fe#4,T3=r.T, +//CONVERT #include "crypto_sign.h" +//CONVERT #include "crypto_hash_sha512.h" +//CONVERT #include "crypto_verify_32.h" +//CONVERT #include "ge.h" +//CONVERT #include "sc.h" + + public static int crypto_sign_open( + Sha512 sha512provider, + byte[] m, long mlen, + byte[] sm, long smlen, + byte[] pk + ) { + byte[] pkcopy = new byte[32]; + byte[] rcopy = new byte[32]; + byte[] scopy = new byte[32]; + byte[] h = new byte[64]; + byte[] rcheck = new byte[32]; + ge_p3 A = new ge_p3(); + ge_p2 R = new ge_p2(); + + if (smlen < 64) return -1; + if ((sm[63] & 224) != 0) return -1; + if (ge_frombytes.ge_frombytes_negate_vartime(A, pk) != 0) return -1; + + byte[] pubkeyhash = new byte[64]; + sha512provider.calculateDigest(pubkeyhash, pk, 32); + + System.arraycopy(pk, 0, pkcopy, 0, 32); + System.arraycopy(sm, 0, rcopy, 0, 32); + System.arraycopy(sm, 32, scopy, 0, 32); + + System.arraycopy(sm, 0, m, 0, (int) smlen); + System.arraycopy(pkcopy, 0, m, 32, 32); + sha512provider.calculateDigest(h, m, smlen); + sc_reduce.sc_reduce(h); + + ge_double_scalarmult.ge_double_scalarmult_vartime(R, h, A, scopy); + ge_tobytes.ge_tobytes(rcheck, R); + if (crypto_verify_32.crypto_verify_32(rcheck, rcopy) == 0) { + System.arraycopy(m, 64, m, 0, (int) (smlen - 64)); + //memset(m + smlen - 64,0,64); + return 0; + } + +//badsig: + //memset(m,0,smlen); + return -1; + } + + +} diff --git a/actor-keygen/src/main/java/im/actor/keygen/curve25519/sc_muladd.java b/actor-keygen/src/main/java/im/actor/keygen/curve25519/sc_muladd.java new file mode 100755 index 0000000000..d94fc2c843 --- /dev/null +++ b/actor-keygen/src/main/java/im/actor/keygen/curve25519/sc_muladd.java @@ -0,0 +1,516 @@ +package im.actor.keygen.curve25519; + +// Disabling Bounds checks for speeding up calculations + +/*-[ +#define J2OBJC_DISABLE_ARRAY_BOUND_CHECKS 1 +]-*/ + +public class sc_muladd { + +//CONVERT #include "sc.h" +//CONVERT #include "long.h" +//CONVERT #include "crypto_uint32.h" +//CONVERT #include "long.h" + + public static long load_3(byte[] in, int index) { + long result; + result = ((long) in[index + 0]) & 0xFF; + result |= (((long) in[index + 1]) << 8) & 0xFF00; + result |= (((long) in[index + 2]) << 16) & 0xFF0000; + return result; + } + + public static long load_4(byte[] in, int index) { + long result; + result = (((long) in[index + 0]) & 0xFF); + result |= ((((long) in[index + 1]) << 8) & 0xFF00); + result |= ((((long) in[index + 2]) << 16) & 0xFF0000); + result |= ((((long) in[index + 3]) << 24) & 0xFF000000L); + return result; + } + +/* +Input: + a[0]+256*a[1]+...+256^31*a[31] = a + b[0]+256*b[1]+...+256^31*b[31] = b + c[0]+256*c[1]+...+256^31*c[31] = c + +Output: + s[0]+256*s[1]+...+256^31*s[31] = (ab+c) mod l + where l = 2^252 + 27742317777372353535851937790883648493. +*/ + + public static void sc_muladd(byte[] s, byte[] a, byte[] b, byte[] c) { + long a0 = 2097151 & load_3(a, 0); + long a1 = 2097151 & (load_4(a, 2) >>> 5); + long a2 = 2097151 & (load_3(a, 5) >>> 2); + long a3 = 2097151 & (load_4(a, 7) >>> 7); + long a4 = 2097151 & (load_4(a, 10) >>> 4); + long a5 = 2097151 & (load_3(a, 13) >>> 1); + long a6 = 2097151 & (load_4(a, 15) >>> 6); + long a7 = 2097151 & (load_3(a, 18) >>> 3); + long a8 = 2097151 & load_3(a, 21); + long a9 = 2097151 & (load_4(a, 23) >>> 5); + long a10 = 2097151 & (load_3(a, 26) >>> 2); + long a11 = (load_4(a, 28) >>> 7); + long b0 = 2097151 & load_3(b, 0); + long b1 = 2097151 & (load_4(b, 2) >>> 5); + long b2 = 2097151 & (load_3(b, 5) >>> 2); + long b3 = 2097151 & (load_4(b, 7) >>> 7); + long b4 = 2097151 & (load_4(b, 10) >>> 4); + long b5 = 2097151 & (load_3(b, 13) >>> 1); + long b6 = 2097151 & (load_4(b, 15) >>> 6); + long b7 = 2097151 & (load_3(b, 18) >>> 3); + long b8 = 2097151 & load_3(b, 21); + long b9 = 2097151 & (load_4(b, 23) >>> 5); + long b10 = 2097151 & (load_3(b, 26) >>> 2); + long b11 = (load_4(b, 28) >>> 7); + long c0 = 2097151 & load_3(c, 0); + long c1 = 2097151 & (load_4(c, 2) >>> 5); + long c2 = 2097151 & (load_3(c, 5) >>> 2); + long c3 = 2097151 & (load_4(c, 7) >>> 7); + long c4 = 2097151 & (load_4(c, 10) >>> 4); + long c5 = 2097151 & (load_3(c, 13) >>> 1); + long c6 = 2097151 & (load_4(c, 15) >>> 6); + long c7 = 2097151 & (load_3(c, 18) >>> 3); + long c8 = 2097151 & load_3(c, 21); + long c9 = 2097151 & (load_4(c, 23) >>> 5); + long c10 = 2097151 & (load_3(c, 26) >>> 2); + long c11 = (load_4(c, 28) >>> 7); + long s0; + long s1; + long s2; + long s3; + long s4; + long s5; + long s6; + long s7; + long s8; + long s9; + long s10; + long s11; + long s12; + long s13; + long s14; + long s15; + long s16; + long s17; + long s18; + long s19; + long s20; + long s21; + long s22; + long s23; + long carry0; + long carry1; + long carry2; + long carry3; + long carry4; + long carry5; + long carry6; + long carry7; + long carry8; + long carry9; + long carry10; + long carry11; + long carry12; + long carry13; + long carry14; + long carry15; + long carry16; + long carry17; + long carry18; + long carry19; + long carry20; + long carry21; + long carry22; + + s0 = c0 + a0 * b0; + s1 = c1 + a0 * b1 + a1 * b0; + s2 = c2 + a0 * b2 + a1 * b1 + a2 * b0; + s3 = c3 + a0 * b3 + a1 * b2 + a2 * b1 + a3 * b0; + s4 = c4 + a0 * b4 + a1 * b3 + a2 * b2 + a3 * b1 + a4 * b0; + s5 = c5 + a0 * b5 + a1 * b4 + a2 * b3 + a3 * b2 + a4 * b1 + a5 * b0; + s6 = c6 + a0 * b6 + a1 * b5 + a2 * b4 + a3 * b3 + a4 * b2 + a5 * b1 + a6 * b0; + s7 = c7 + a0 * b7 + a1 * b6 + a2 * b5 + a3 * b4 + a4 * b3 + a5 * b2 + a6 * b1 + a7 * b0; + s8 = c8 + a0 * b8 + a1 * b7 + a2 * b6 + a3 * b5 + a4 * b4 + a5 * b3 + a6 * b2 + a7 * b1 + a8 * b0; + s9 = c9 + a0 * b9 + a1 * b8 + a2 * b7 + a3 * b6 + a4 * b5 + a5 * b4 + a6 * b3 + a7 * b2 + a8 * b1 + a9 * b0; + s10 = c10 + a0 * b10 + a1 * b9 + a2 * b8 + a3 * b7 + a4 * b6 + a5 * b5 + a6 * b4 + a7 * b3 + a8 * b2 + a9 * b1 + a10 * b0; + s11 = c11 + a0 * b11 + a1 * b10 + a2 * b9 + a3 * b8 + a4 * b7 + a5 * b6 + a6 * b5 + a7 * b4 + a8 * b3 + a9 * b2 + a10 * b1 + a11 * b0; + s12 = a1 * b11 + a2 * b10 + a3 * b9 + a4 * b8 + a5 * b7 + a6 * b6 + a7 * b5 + a8 * b4 + a9 * b3 + a10 * b2 + a11 * b1; + s13 = a2 * b11 + a3 * b10 + a4 * b9 + a5 * b8 + a6 * b7 + a7 * b6 + a8 * b5 + a9 * b4 + a10 * b3 + a11 * b2; + s14 = a3 * b11 + a4 * b10 + a5 * b9 + a6 * b8 + a7 * b7 + a8 * b6 + a9 * b5 + a10 * b4 + a11 * b3; + s15 = a4 * b11 + a5 * b10 + a6 * b9 + a7 * b8 + a8 * b7 + a9 * b6 + a10 * b5 + a11 * b4; + s16 = a5 * b11 + a6 * b10 + a7 * b9 + a8 * b8 + a9 * b7 + a10 * b6 + a11 * b5; + s17 = a6 * b11 + a7 * b10 + a8 * b9 + a9 * b8 + a10 * b7 + a11 * b6; + s18 = a7 * b11 + a8 * b10 + a9 * b9 + a10 * b8 + a11 * b7; + s19 = a8 * b11 + a9 * b10 + a10 * b9 + a11 * b8; + s20 = a9 * b11 + a10 * b10 + a11 * b9; + s21 = a10 * b11 + a11 * b10; + s22 = a11 * b11; + s23 = 0; + + carry0 = (s0 + (1 << 20)) >> 21; + s1 += carry0; + s0 -= carry0 << 21; + carry2 = (s2 + (1 << 20)) >> 21; + s3 += carry2; + s2 -= carry2 << 21; + carry4 = (s4 + (1 << 20)) >> 21; + s5 += carry4; + s4 -= carry4 << 21; + carry6 = (s6 + (1 << 20)) >> 21; + s7 += carry6; + s6 -= carry6 << 21; + carry8 = (s8 + (1 << 20)) >> 21; + s9 += carry8; + s8 -= carry8 << 21; + carry10 = (s10 + (1 << 20)) >> 21; + s11 += carry10; + s10 -= carry10 << 21; + carry12 = (s12 + (1 << 20)) >> 21; + s13 += carry12; + s12 -= carry12 << 21; + carry14 = (s14 + (1 << 20)) >> 21; + s15 += carry14; + s14 -= carry14 << 21; + carry16 = (s16 + (1 << 20)) >> 21; + s17 += carry16; + s16 -= carry16 << 21; + carry18 = (s18 + (1 << 20)) >> 21; + s19 += carry18; + s18 -= carry18 << 21; + carry20 = (s20 + (1 << 20)) >> 21; + s21 += carry20; + s20 -= carry20 << 21; + carry22 = (s22 + (1 << 20)) >> 21; + s23 += carry22; + s22 -= carry22 << 21; + + carry1 = (s1 + (1 << 20)) >> 21; + s2 += carry1; + s1 -= carry1 << 21; + carry3 = (s3 + (1 << 20)) >> 21; + s4 += carry3; + s3 -= carry3 << 21; + carry5 = (s5 + (1 << 20)) >> 21; + s6 += carry5; + s5 -= carry5 << 21; + carry7 = (s7 + (1 << 20)) >> 21; + s8 += carry7; + s7 -= carry7 << 21; + carry9 = (s9 + (1 << 20)) >> 21; + s10 += carry9; + s9 -= carry9 << 21; + carry11 = (s11 + (1 << 20)) >> 21; + s12 += carry11; + s11 -= carry11 << 21; + carry13 = (s13 + (1 << 20)) >> 21; + s14 += carry13; + s13 -= carry13 << 21; + carry15 = (s15 + (1 << 20)) >> 21; + s16 += carry15; + s15 -= carry15 << 21; + carry17 = (s17 + (1 << 20)) >> 21; + s18 += carry17; + s17 -= carry17 << 21; + carry19 = (s19 + (1 << 20)) >> 21; + s20 += carry19; + s19 -= carry19 << 21; + carry21 = (s21 + (1 << 20)) >> 21; + s22 += carry21; + s21 -= carry21 << 21; + + s11 += s23 * 666643; + s12 += s23 * 470296; + s13 += s23 * 654183; + s14 -= s23 * 997805; + s15 += s23 * 136657; + s16 -= s23 * 683901; + s23 = 0; + + s10 += s22 * 666643; + s11 += s22 * 470296; + s12 += s22 * 654183; + s13 -= s22 * 997805; + s14 += s22 * 136657; + s15 -= s22 * 683901; + s22 = 0; + + s9 += s21 * 666643; + s10 += s21 * 470296; + s11 += s21 * 654183; + s12 -= s21 * 997805; + s13 += s21 * 136657; + s14 -= s21 * 683901; + s21 = 0; + + s8 += s20 * 666643; + s9 += s20 * 470296; + s10 += s20 * 654183; + s11 -= s20 * 997805; + s12 += s20 * 136657; + s13 -= s20 * 683901; + s20 = 0; + + s7 += s19 * 666643; + s8 += s19 * 470296; + s9 += s19 * 654183; + s10 -= s19 * 997805; + s11 += s19 * 136657; + s12 -= s19 * 683901; + s19 = 0; + + s6 += s18 * 666643; + s7 += s18 * 470296; + s8 += s18 * 654183; + s9 -= s18 * 997805; + s10 += s18 * 136657; + s11 -= s18 * 683901; + s18 = 0; + + carry6 = (s6 + (1 << 20)) >> 21; + s7 += carry6; + s6 -= carry6 << 21; + carry8 = (s8 + (1 << 20)) >> 21; + s9 += carry8; + s8 -= carry8 << 21; + carry10 = (s10 + (1 << 20)) >> 21; + s11 += carry10; + s10 -= carry10 << 21; + carry12 = (s12 + (1 << 20)) >> 21; + s13 += carry12; + s12 -= carry12 << 21; + carry14 = (s14 + (1 << 20)) >> 21; + s15 += carry14; + s14 -= carry14 << 21; + carry16 = (s16 + (1 << 20)) >> 21; + s17 += carry16; + s16 -= carry16 << 21; + + carry7 = (s7 + (1 << 20)) >> 21; + s8 += carry7; + s7 -= carry7 << 21; + carry9 = (s9 + (1 << 20)) >> 21; + s10 += carry9; + s9 -= carry9 << 21; + carry11 = (s11 + (1 << 20)) >> 21; + s12 += carry11; + s11 -= carry11 << 21; + carry13 = (s13 + (1 << 20)) >> 21; + s14 += carry13; + s13 -= carry13 << 21; + carry15 = (s15 + (1 << 20)) >> 21; + s16 += carry15; + s15 -= carry15 << 21; + + s5 += s17 * 666643; + s6 += s17 * 470296; + s7 += s17 * 654183; + s8 -= s17 * 997805; + s9 += s17 * 136657; + s10 -= s17 * 683901; + s17 = 0; + + s4 += s16 * 666643; + s5 += s16 * 470296; + s6 += s16 * 654183; + s7 -= s16 * 997805; + s8 += s16 * 136657; + s9 -= s16 * 683901; + s16 = 0; + + s3 += s15 * 666643; + s4 += s15 * 470296; + s5 += s15 * 654183; + s6 -= s15 * 997805; + s7 += s15 * 136657; + s8 -= s15 * 683901; + s15 = 0; + + s2 += s14 * 666643; + s3 += s14 * 470296; + s4 += s14 * 654183; + s5 -= s14 * 997805; + s6 += s14 * 136657; + s7 -= s14 * 683901; + s14 = 0; + + s1 += s13 * 666643; + s2 += s13 * 470296; + s3 += s13 * 654183; + s4 -= s13 * 997805; + s5 += s13 * 136657; + s6 -= s13 * 683901; + s13 = 0; + + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + s12 = 0; + + carry0 = (s0 + (1 << 20)) >> 21; + s1 += carry0; + s0 -= carry0 << 21; + carry2 = (s2 + (1 << 20)) >> 21; + s3 += carry2; + s2 -= carry2 << 21; + carry4 = (s4 + (1 << 20)) >> 21; + s5 += carry4; + s4 -= carry4 << 21; + carry6 = (s6 + (1 << 20)) >> 21; + s7 += carry6; + s6 -= carry6 << 21; + carry8 = (s8 + (1 << 20)) >> 21; + s9 += carry8; + s8 -= carry8 << 21; + carry10 = (s10 + (1 << 20)) >> 21; + s11 += carry10; + s10 -= carry10 << 21; + + carry1 = (s1 + (1 << 20)) >> 21; + s2 += carry1; + s1 -= carry1 << 21; + carry3 = (s3 + (1 << 20)) >> 21; + s4 += carry3; + s3 -= carry3 << 21; + carry5 = (s5 + (1 << 20)) >> 21; + s6 += carry5; + s5 -= carry5 << 21; + carry7 = (s7 + (1 << 20)) >> 21; + s8 += carry7; + s7 -= carry7 << 21; + carry9 = (s9 + (1 << 20)) >> 21; + s10 += carry9; + s9 -= carry9 << 21; + carry11 = (s11 + (1 << 20)) >> 21; + s12 += carry11; + s11 -= carry11 << 21; + + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + s12 = 0; + + carry0 = s0 >> 21; + s1 += carry0; + s0 -= carry0 << 21; + carry1 = s1 >> 21; + s2 += carry1; + s1 -= carry1 << 21; + carry2 = s2 >> 21; + s3 += carry2; + s2 -= carry2 << 21; + carry3 = s3 >> 21; + s4 += carry3; + s3 -= carry3 << 21; + carry4 = s4 >> 21; + s5 += carry4; + s4 -= carry4 << 21; + carry5 = s5 >> 21; + s6 += carry5; + s5 -= carry5 << 21; + carry6 = s6 >> 21; + s7 += carry6; + s6 -= carry6 << 21; + carry7 = s7 >> 21; + s8 += carry7; + s7 -= carry7 << 21; + carry8 = s8 >> 21; + s9 += carry8; + s8 -= carry8 << 21; + carry9 = s9 >> 21; + s10 += carry9; + s9 -= carry9 << 21; + carry10 = s10 >> 21; + s11 += carry10; + s10 -= carry10 << 21; + carry11 = s11 >> 21; + s12 += carry11; + s11 -= carry11 << 21; + + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + s12 = 0; + + carry0 = s0 >> 21; + s1 += carry0; + s0 -= carry0 << 21; + carry1 = s1 >> 21; + s2 += carry1; + s1 -= carry1 << 21; + carry2 = s2 >> 21; + s3 += carry2; + s2 -= carry2 << 21; + carry3 = s3 >> 21; + s4 += carry3; + s3 -= carry3 << 21; + carry4 = s4 >> 21; + s5 += carry4; + s4 -= carry4 << 21; + carry5 = s5 >> 21; + s6 += carry5; + s5 -= carry5 << 21; + carry6 = s6 >> 21; + s7 += carry6; + s6 -= carry6 << 21; + carry7 = s7 >> 21; + s8 += carry7; + s7 -= carry7 << 21; + carry8 = s8 >> 21; + s9 += carry8; + s8 -= carry8 << 21; + carry9 = s9 >> 21; + s10 += carry9; + s9 -= carry9 << 21; + carry10 = s10 >> 21; + s11 += carry10; + s10 -= carry10 << 21; + + s[0] = (byte) (s0 >> 0); + s[1] = (byte) (s0 >> 8); + s[2] = (byte) ((s0 >> 16) | (s1 << 5)); + s[3] = (byte) (s1 >> 3); + s[4] = (byte) (s1 >> 11); + s[5] = (byte) ((s1 >> 19) | (s2 << 2)); + s[6] = (byte) (s2 >> 6); + s[7] = (byte) ((s2 >> 14) | (s3 << 7)); + s[8] = (byte) (s3 >> 1); + s[9] = (byte) (s3 >> 9); + s[10] = (byte) ((s3 >> 17) | (s4 << 4)); + s[11] = (byte) (s4 >> 4); + s[12] = (byte) (s4 >> 12); + s[13] = (byte) ((s4 >> 20) | (s5 << 1)); + s[14] = (byte) (s5 >> 7); + s[15] = (byte) ((s5 >> 15) | (s6 << 6)); + s[16] = (byte) (s6 >> 2); + s[17] = (byte) (s6 >> 10); + s[18] = (byte) ((s6 >> 18) | (s7 << 3)); + s[19] = (byte) (s7 >> 5); + s[20] = (byte) (s7 >> 13); + s[21] = (byte) (s8 >> 0); + s[22] = (byte) (s8 >> 8); + s[23] = (byte) ((s8 >> 16) | (s9 << 5)); + s[24] = (byte) (s9 >> 3); + s[25] = (byte) (s9 >> 11); + s[26] = (byte) ((s9 >> 19) | (s10 << 2)); + s[27] = (byte) (s10 >> 6); + s[28] = (byte) ((s10 >> 14) | (s11 << 7)); + s[29] = (byte) (s11 >> 1); + s[30] = (byte) (s11 >> 9); + s[31] = (byte) (s11 >> 17); + } + + +} diff --git a/actor-keygen/src/main/java/im/actor/keygen/curve25519/sc_reduce.java b/actor-keygen/src/main/java/im/actor/keygen/curve25519/sc_reduce.java new file mode 100755 index 0000000000..0394ef92e9 --- /dev/null +++ b/actor-keygen/src/main/java/im/actor/keygen/curve25519/sc_reduce.java @@ -0,0 +1,377 @@ +package im.actor.keygen.curve25519; + +// Disabling Bounds checks for speeding up calculations + +/*-[ +#define J2OBJC_DISABLE_ARRAY_BOUND_CHECKS 1 +]-*/ + +public class sc_reduce { + +//CONVERT #include "sc.h" +//CONVERT #include "long.h" +//CONVERT #include "crypto_uint32.h" +//CONVERT #include "long.h" + + public static long load_3(byte[] in, int index) { + long result; + result = ((long) in[index + 0]) & 0xFF; + result |= (((long) in[index + 1]) << 8) & 0xFF00; + result |= (((long) in[index + 2]) << 16) & 0xFF0000; + return result; + } + + public static long load_4(byte[] in, int index) { + long result; + result = (((long) in[index + 0]) & 0xFF); + result |= ((((long) in[index + 1]) << 8) & 0xFF00); + result |= ((((long) in[index + 2]) << 16) & 0xFF0000); + result |= ((((long) in[index + 3]) << 24) & 0xFF000000L); + return result; + } + +/* +Input: + s[0]+256*s[1]+...+256^63*s[63] = s + +Output: + s[0]+256*s[1]+...+256^31*s[31] = s mod l + where l = 2^252 + 27742317777372353535851937790883648493. + Overwrites s in place. +*/ + + public static void sc_reduce(byte[] s) { + long s0 = 2097151 & load_3(s, 0); + long s1 = 2097151 & (load_4(s, 2) >>> 5); + long s2 = 2097151 & (load_3(s, 5) >>> 2); + long s3 = 2097151 & (load_4(s, 7) >>> 7); + long s4 = 2097151 & (load_4(s, 10) >>> 4); + long s5 = 2097151 & (load_3(s, 13) >>> 1); + long s6 = 2097151 & (load_4(s, 15) >>> 6); + long s7 = 2097151 & (load_3(s, 18) >>> 3); + long s8 = 2097151 & load_3(s, 21); + long s9 = 2097151 & (load_4(s, 23) >>> 5); + long s10 = 2097151 & (load_3(s, 26) >>> 2); + long s11 = 2097151 & (load_4(s, 28) >>> 7); + long s12 = 2097151 & (load_4(s, 31) >>> 4); + long s13 = 2097151 & (load_3(s, 34) >>> 1); + long s14 = 2097151 & (load_4(s, 36) >>> 6); + long s15 = 2097151 & (load_3(s, 39) >>> 3); + long s16 = 2097151 & load_3(s, 42); + long s17 = 2097151 & (load_4(s, 44) >>> 5); + long s18 = 2097151 & (load_3(s, 47) >>> 2); + long s19 = 2097151 & (load_4(s, 49) >>> 7); + long s20 = 2097151 & (load_4(s, 52) >>> 4); + long s21 = 2097151 & (load_3(s, 55) >>> 1); + long s22 = 2097151 & (load_4(s, 57) >>> 6); + long s23 = (load_4(s, 60) >>> 3); + long carry0; + long carry1; + long carry2; + long carry3; + long carry4; + long carry5; + long carry6; + long carry7; + long carry8; + long carry9; + long carry10; + long carry11; + long carry12; + long carry13; + long carry14; + long carry15; + long carry16; + + s11 += s23 * 666643; + s12 += s23 * 470296; + s13 += s23 * 654183; + s14 -= s23 * 997805; + s15 += s23 * 136657; + s16 -= s23 * 683901; + s23 = 0; + + s10 += s22 * 666643; + s11 += s22 * 470296; + s12 += s22 * 654183; + s13 -= s22 * 997805; + s14 += s22 * 136657; + s15 -= s22 * 683901; + s22 = 0; + + s9 += s21 * 666643; + s10 += s21 * 470296; + s11 += s21 * 654183; + s12 -= s21 * 997805; + s13 += s21 * 136657; + s14 -= s21 * 683901; + s21 = 0; + + s8 += s20 * 666643; + s9 += s20 * 470296; + s10 += s20 * 654183; + s11 -= s20 * 997805; + s12 += s20 * 136657; + s13 -= s20 * 683901; + s20 = 0; + + s7 += s19 * 666643; + s8 += s19 * 470296; + s9 += s19 * 654183; + s10 -= s19 * 997805; + s11 += s19 * 136657; + s12 -= s19 * 683901; + s19 = 0; + + s6 += s18 * 666643; + s7 += s18 * 470296; + s8 += s18 * 654183; + s9 -= s18 * 997805; + s10 += s18 * 136657; + s11 -= s18 * 683901; + s18 = 0; + + carry6 = (s6 + (1 << 20)) >> 21; + s7 += carry6; + s6 -= carry6 << 21; + carry8 = (s8 + (1 << 20)) >> 21; + s9 += carry8; + s8 -= carry8 << 21; + carry10 = (s10 + (1 << 20)) >> 21; + s11 += carry10; + s10 -= carry10 << 21; + carry12 = (s12 + (1 << 20)) >> 21; + s13 += carry12; + s12 -= carry12 << 21; + carry14 = (s14 + (1 << 20)) >> 21; + s15 += carry14; + s14 -= carry14 << 21; + carry16 = (s16 + (1 << 20)) >> 21; + s17 += carry16; + s16 -= carry16 << 21; + + carry7 = (s7 + (1 << 20)) >> 21; + s8 += carry7; + s7 -= carry7 << 21; + carry9 = (s9 + (1 << 20)) >> 21; + s10 += carry9; + s9 -= carry9 << 21; + carry11 = (s11 + (1 << 20)) >> 21; + s12 += carry11; + s11 -= carry11 << 21; + carry13 = (s13 + (1 << 20)) >> 21; + s14 += carry13; + s13 -= carry13 << 21; + carry15 = (s15 + (1 << 20)) >> 21; + s16 += carry15; + s15 -= carry15 << 21; + + s5 += s17 * 666643; + s6 += s17 * 470296; + s7 += s17 * 654183; + s8 -= s17 * 997805; + s9 += s17 * 136657; + s10 -= s17 * 683901; + s17 = 0; + + s4 += s16 * 666643; + s5 += s16 * 470296; + s6 += s16 * 654183; + s7 -= s16 * 997805; + s8 += s16 * 136657; + s9 -= s16 * 683901; + s16 = 0; + + s3 += s15 * 666643; + s4 += s15 * 470296; + s5 += s15 * 654183; + s6 -= s15 * 997805; + s7 += s15 * 136657; + s8 -= s15 * 683901; + s15 = 0; + + s2 += s14 * 666643; + s3 += s14 * 470296; + s4 += s14 * 654183; + s5 -= s14 * 997805; + s6 += s14 * 136657; + s7 -= s14 * 683901; + s14 = 0; + + s1 += s13 * 666643; + s2 += s13 * 470296; + s3 += s13 * 654183; + s4 -= s13 * 997805; + s5 += s13 * 136657; + s6 -= s13 * 683901; + s13 = 0; + + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + s12 = 0; + + carry0 = (s0 + (1 << 20)) >> 21; + s1 += carry0; + s0 -= carry0 << 21; + carry2 = (s2 + (1 << 20)) >> 21; + s3 += carry2; + s2 -= carry2 << 21; + carry4 = (s4 + (1 << 20)) >> 21; + s5 += carry4; + s4 -= carry4 << 21; + carry6 = (s6 + (1 << 20)) >> 21; + s7 += carry6; + s6 -= carry6 << 21; + carry8 = (s8 + (1 << 20)) >> 21; + s9 += carry8; + s8 -= carry8 << 21; + carry10 = (s10 + (1 << 20)) >> 21; + s11 += carry10; + s10 -= carry10 << 21; + + carry1 = (s1 + (1 << 20)) >> 21; + s2 += carry1; + s1 -= carry1 << 21; + carry3 = (s3 + (1 << 20)) >> 21; + s4 += carry3; + s3 -= carry3 << 21; + carry5 = (s5 + (1 << 20)) >> 21; + s6 += carry5; + s5 -= carry5 << 21; + carry7 = (s7 + (1 << 20)) >> 21; + s8 += carry7; + s7 -= carry7 << 21; + carry9 = (s9 + (1 << 20)) >> 21; + s10 += carry9; + s9 -= carry9 << 21; + carry11 = (s11 + (1 << 20)) >> 21; + s12 += carry11; + s11 -= carry11 << 21; + + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + s12 = 0; + + carry0 = s0 >> 21; + s1 += carry0; + s0 -= carry0 << 21; + carry1 = s1 >> 21; + s2 += carry1; + s1 -= carry1 << 21; + carry2 = s2 >> 21; + s3 += carry2; + s2 -= carry2 << 21; + carry3 = s3 >> 21; + s4 += carry3; + s3 -= carry3 << 21; + carry4 = s4 >> 21; + s5 += carry4; + s4 -= carry4 << 21; + carry5 = s5 >> 21; + s6 += carry5; + s5 -= carry5 << 21; + carry6 = s6 >> 21; + s7 += carry6; + s6 -= carry6 << 21; + carry7 = s7 >> 21; + s8 += carry7; + s7 -= carry7 << 21; + carry8 = s8 >> 21; + s9 += carry8; + s8 -= carry8 << 21; + carry9 = s9 >> 21; + s10 += carry9; + s9 -= carry9 << 21; + carry10 = s10 >> 21; + s11 += carry10; + s10 -= carry10 << 21; + carry11 = s11 >> 21; + s12 += carry11; + s11 -= carry11 << 21; + + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + s12 = 0; + + carry0 = s0 >> 21; + s1 += carry0; + s0 -= carry0 << 21; + carry1 = s1 >> 21; + s2 += carry1; + s1 -= carry1 << 21; + carry2 = s2 >> 21; + s3 += carry2; + s2 -= carry2 << 21; + carry3 = s3 >> 21; + s4 += carry3; + s3 -= carry3 << 21; + carry4 = s4 >> 21; + s5 += carry4; + s4 -= carry4 << 21; + carry5 = s5 >> 21; + s6 += carry5; + s5 -= carry5 << 21; + carry6 = s6 >> 21; + s7 += carry6; + s6 -= carry6 << 21; + carry7 = s7 >> 21; + s8 += carry7; + s7 -= carry7 << 21; + carry8 = s8 >> 21; + s9 += carry8; + s8 -= carry8 << 21; + carry9 = s9 >> 21; + s10 += carry9; + s9 -= carry9 << 21; + carry10 = s10 >> 21; + s11 += carry10; + s10 -= carry10 << 21; + + s[0] = (byte) (s0 >> 0); + s[1] = (byte) (s0 >> 8); + s[2] = (byte) ((s0 >> 16) | (s1 << 5)); + s[3] = (byte) (s1 >> 3); + s[4] = (byte) (s1 >> 11); + s[5] = (byte) ((s1 >> 19) | (s2 << 2)); + s[6] = (byte) (s2 >> 6); + s[7] = (byte) ((s2 >> 14) | (s3 << 7)); + s[8] = (byte) (s3 >> 1); + s[9] = (byte) (s3 >> 9); + s[10] = (byte) ((s3 >> 17) | (s4 << 4)); + s[11] = (byte) (s4 >> 4); + s[12] = (byte) (s4 >> 12); + s[13] = (byte) ((s4 >> 20) | (s5 << 1)); + s[14] = (byte) (s5 >> 7); + s[15] = (byte) ((s5 >> 15) | (s6 << 6)); + s[16] = (byte) (s6 >> 2); + s[17] = (byte) (s6 >> 10); + s[18] = (byte) ((s6 >> 18) | (s7 << 3)); + s[19] = (byte) (s7 >> 5); + s[20] = (byte) (s7 >> 13); + s[21] = (byte) (s8 >> 0); + s[22] = (byte) (s8 >> 8); + s[23] = (byte) ((s8 >> 16) | (s9 << 5)); + s[24] = (byte) (s9 >> 3); + s[25] = (byte) (s9 >> 11); + s[26] = (byte) ((s9 >> 19) | (s10 << 2)); + s[27] = (byte) (s10 >> 6); + s[28] = (byte) ((s10 >> 14) | (s11 << 7)); + s[29] = (byte) (s11 >> 1); + s[30] = (byte) (s11 >> 9); + s[31] = (byte) (s11 >> 17); + } + + +} diff --git a/actor-keygen/src/main/java/im/actor/keygen/curve25519/scalarmult.java b/actor-keygen/src/main/java/im/actor/keygen/curve25519/scalarmult.java new file mode 100755 index 0000000000..df134338be --- /dev/null +++ b/actor-keygen/src/main/java/im/actor/keygen/curve25519/scalarmult.java @@ -0,0 +1,200 @@ +package im.actor.keygen.curve25519; + +// Disabling Bounds checks for speeding up calculations + +/*-[ +#define J2OBJC_DISABLE_ARRAY_BOUND_CHECKS 1 +]-*/ + +public class scalarmult { + +//CONVERT #include "crypto_scalarmult.h" +//CONVERT #include "fe.h" + + public static int crypto_scalarmult(byte[] q, + byte[] n, + byte[] p) { + byte[] e = new byte[32]; + int i; + int[] x1 = new int[10]; + int[] x2 = new int[10]; + int[] z2 = new int[10]; + int[] x3 = new int[10]; + int[] z3 = new int[10]; + int[] tmp0 = new int[10]; + int[] tmp1 = new int[10]; + int pos; + int swap; + int b; + + for (i = 0; i < 32; ++i) e[i] = n[i]; +// e[0] &= 248; +// e[31] &= 127; +// e[31] |= 64; + fe_frombytes.fe_frombytes(x1, p); + fe_1.fe_1(x2); + fe_0.fe_0(z2); + fe_copy.fe_copy(x3, x1); + fe_1.fe_1(z3); + + swap = 0; + for (pos = 254; pos >= 0; --pos) { + b = e[pos / 8] >>> (pos & 7); + b &= 1; + swap ^= b; + fe_cswap.fe_cswap(x2, x3, swap); + fe_cswap.fe_cswap(z2, z3, swap); + swap = b; +//CONVERT #include "montgomery.h" + +/* qhasm: fe X2 */ + +/* qhasm: fe Z2 */ + +/* qhasm: fe X3 */ + +/* qhasm: fe Z3 */ + +/* qhasm: fe X4 */ + +/* qhasm: fe Z4 */ + +/* qhasm: fe X5 */ + +/* qhasm: fe Z5 */ + +/* qhasm: fe A */ + +/* qhasm: fe B */ + +/* qhasm: fe C */ + +/* qhasm: fe D */ + +/* qhasm: fe E */ + +/* qhasm: fe AA */ + +/* qhasm: fe BB */ + +/* qhasm: fe DA */ + +/* qhasm: fe CB */ + +/* qhasm: fe t0 */ + +/* qhasm: fe t1 */ + +/* qhasm: fe t2 */ + +/* qhasm: fe t3 */ + +/* qhasm: fe t4 */ + +/* qhasm: enter ladder */ + +/* qhasm: D = X3-Z3 */ +/* asm 1: fe_sub.fe_sub(>D=fe#5,D=tmp0,B=fe#6,B=tmp1,A=fe#1,A=x2,C=fe#2,C=z2,DA=fe#4,DA=z3,CB=fe#2,CB=z2,BB=fe#5,BB=tmp0,AA=fe#6,AA=tmp1,t0=fe#3,t0=x3,t1=fe#2,t1=z2,X4=fe#1,X4=x2,E=fe#6,E=tmp1,t2=fe#2,t2=z2,t3=fe#4,t3=z3,X5=fe#3,X5=x3,t4=fe#5,t4=tmp0,Z5=fe#4,x1,Z5=z3,x1,Z4=fe#2,Z4=z2, +//CONVERT #include "crypto_sign.h" +//CONVERT #include "crypto_hash_sha512.h" +//CONVERT #include "ge.h" +//CONVERT #include "sc.h" +//CONVERT #include "zeroize.h" + + /* NEW: Compare to pristine crypto_sign() + Uses explicit private key for nonce derivation and as scalar, + instead of deriving both from a master key. + */ + static int crypto_sign_modified( + Sha512 sha512provider, + byte[] sm, + byte[] m, long mlen, + byte[] sk, byte[] pk, + byte[] random + ) { + byte[] nonce = new byte[64]; + byte[] hram = new byte[64]; + ge_p3 R = new ge_p3(); + int count = 0; + + System.arraycopy(m, 0, sm, 64, (int) mlen); + System.arraycopy(sk, 0, sm, 32, 32); + + /* NEW : add prefix to separate hash uses - see .h */ + sm[0] = (byte) 0xFE; + for (count = 1; count < 32; count++) + sm[count] = (byte) 0xFF; + + /* NEW: add suffix of random data */ + System.arraycopy(random, 0, sm, (int) (mlen + 64), 64); + + sha512provider.calculateDigest(nonce, sm, mlen + 128); + System.arraycopy(pk, 0, sm, 32, 32); + + sc_reduce.sc_reduce(nonce); + ge_scalarmult_base.ge_scalarmult_base(R, nonce); + ge_p3_tobytes.ge_p3_tobytes(sm, R); + + sha512provider.calculateDigest(hram, sm, mlen + 64); + sc_reduce.sc_reduce(hram); + byte[] S = new byte[32]; + sc_muladd.sc_muladd(S, hram, sk, nonce); /* NEW: Use privkey directly */ + System.arraycopy(S, 0, sm, 32, 32); + + return 0; + } + + +} diff --git a/settings.gradle b/settings.gradle index 62051e33b9..bdb45b2558 100644 --- a/settings.gradle +++ b/settings.gradle @@ -34,6 +34,12 @@ include ":actor-sdk:sdk-core-android:android-google-maps" include ":actor-sdk:sdk-core-ios" +// +// SDK KeyGen +// + +include ":actor-keygen" + // // SDK Command Line Client // From afc1d2e35a6e9d6aa90fca3ab817edae431b5898 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Wed, 14 Sep 2016 02:38:05 +0300 Subject: [PATCH 340/414] feat(keygen): Added Docker build --- actor-keygen/Dockerfile | 9 +++++++++ actor-keygen/docker.sh | 18 ++++++++++++++++++ .../src/main/java/im/actor/keygen/Main.java | 3 ++- 3 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 actor-keygen/Dockerfile create mode 100755 actor-keygen/docker.sh diff --git a/actor-keygen/Dockerfile b/actor-keygen/Dockerfile new file mode 100644 index 0000000000..9b81a26b14 --- /dev/null +++ b/actor-keygen/Dockerfile @@ -0,0 +1,9 @@ +FROM actor/base-java:latest +MAINTAINER Steve Kite + +ADD build/docker/bin/* /opt/actor-keygen/bin/ +ADD build/docker/lib/* /opt/actor-keygen/lib/ + +WORKDIR "/keygen" + +CMD ["/opt/actor-keygen/bin/actor-keygen"] \ No newline at end of file diff --git a/actor-keygen/docker.sh b/actor-keygen/docker.sh new file mode 100755 index 0000000000..566f23c5ec --- /dev/null +++ b/actor-keygen/docker.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +cd .. +./gradlew actor-keygen:assembleDist +cd actor-keygen + +# Unpacking Distrib +cd build +rm -fr docker +mkdir -p docker +cd distributions +rm -fr actor-keygen +unzip actor-keygen.zip +cp -r actor-keygen/* ../docker/ +cd ../.. + +# Building docker +docker build -t actor/keygen . \ No newline at end of file diff --git a/actor-keygen/src/main/java/im/actor/keygen/Main.java b/actor-keygen/src/main/java/im/actor/keygen/Main.java index ad75ae443f..8c9b0a5089 100644 --- a/actor-keygen/src/main/java/im/actor/keygen/Main.java +++ b/actor-keygen/src/main/java/im/actor/keygen/Main.java @@ -7,6 +7,7 @@ import java.security.DigestException; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; +import java.util.Base64; public class Main { public static void main(String[] args) throws DigestException, NoSuchAlgorithmException, IOException { @@ -25,6 +26,6 @@ public static void main(String[] args) throws DigestException, NoSuchAlgorithmEx FileUtils.writeByteArrayToFile(pubFile, keyPair.getPublicKey()); FileUtils.writeByteArrayToFile(keyFile, keyPair.getPrivateKey()); } - System.out.println("Shared Secret: " + Hex.toHex(secureRandom.generateSeed(64))); + System.out.println("Shared Secret: " + Base64.getEncoder().encodeToString(secureRandom.generateSeed(64))); } } \ No newline at end of file From 0db43285b53032df936efd9a79cc19fd4845c5f5 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Wed, 14 Sep 2016 02:54:47 +0300 Subject: [PATCH 341/414] Update server.conf --- .../src/docker/var/lib/actor/conf/server.conf | 38 ++++++++++++++----- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/actor-server/src/docker/var/lib/actor/conf/server.conf b/actor-server/src/docker/var/lib/actor/conf/server.conf index cf9156ad89..c4487fef2b 100644 --- a/actor-server/src/docker/var/lib/actor/conf/server.conf +++ b/actor-server/src/docker/var/lib/actor/conf/server.conf @@ -1,20 +1,16 @@ // We need it in application.conf because reference.conf can't refer to application.conf, this is a work-around -modules { - files { - adapter: "im.actor.server.file.local.LocalFileStorageAdapter" - } -} +secret: ${?ACTOR_SECRET} services { postgresql { host: "postgres" host: ${?ACTOR_DB_HOST} - db: postgres + db: actor db: ${?ACTOR_DB_NAME} - user: "postgres" + user: "actor" user: ${?ACTOR_DB_USER} password: "" @@ -27,12 +23,36 @@ services { } file-storage { - location: "/var/lib/actor/files" + location: "/files" location: ${?ACTOR_FILESTORAGE_LOCATION} } } -secret: ${?ACTOR_SECRET} +modules { + files { + adapter: "im.actor.server.file.local.LocalFileStorageAdapter" + } + security { + server-keys: [ + { + public: "/keys/actor-key-0.pub" + private: "/keys/actor-key-0.key" + } + { + public: "/keys/actor-key-1.pub" + private: "/keys/actor-key-1.key" + } + { + public: "/keys/actor-key-2.pub" + private: "/keys/actor-key-2.key" + } + { + public: "/keys/actor-key-3.pub" + private: "/keys/actor-key-3.key" + } + ] + } +} akka { log-config-on-start: true From dc6d4bb41f659eeb9956384ca2da82543c8804de Mon Sep 17 00:00:00 2001 From: kor_ka Date: Mon, 12 Sep 2016 19:38:27 +0300 Subject: [PATCH 342/414] wip(android): emoji keyboard under soft keyboard --- .../conversation/ChatFragment.java | 5 + .../conversation/KeyboardLayout.java | 69 ++++++ .../inputbar/InputBarFragment.java | 18 +- .../sdk/view/emoji/keyboard/BaseKeyboard.java | 209 ++++++++++-------- .../emoji/keyboard/emoji/EmojiKeyboard.java | 5 +- .../src/main/res/layout/fragment_chat.xml | 103 +++++---- 6 files changed, 260 insertions(+), 149 deletions(-) create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/KeyboardLayout.java diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatFragment.java index a0c1ebb1c5..90f2a7154f 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatFragment.java @@ -1,6 +1,7 @@ package im.actor.sdk.controllers.conversation; import android.app.Activity; +import android.content.res.Configuration; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; @@ -256,6 +257,10 @@ public boolean onBackPressed() { } } + if (findInputBar().onBackPressed()) { + return true; + } + // Message Edit if (editRid != 0) { editRid = 0; diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/KeyboardLayout.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/KeyboardLayout.java new file mode 100644 index 0000000000..860b37f711 --- /dev/null +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/KeyboardLayout.java @@ -0,0 +1,69 @@ +package im.actor.sdk.controllers.conversation; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.FrameLayout; +import android.widget.RelativeLayout; + +import im.actor.sdk.R; + +public class KeyboardLayout extends FrameLayout { + boolean showInternal = false; + private RelativeLayout container; + private int keyboardHeight; + + public KeyboardLayout(Context context) { + super(context); + } + + public KeyboardLayout(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public KeyboardLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public KeyboardLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + if (container != null) { + if (!showInternal) { + if (container.getPaddingBottom() != 0) { + container.setPadding(0, 0, 0, 0); + } + } + } + + super.onLayout(changed, left, top, right, bottom); + + if (container != null) { + if (showInternal) { + if (container.getPaddingBottom() != keyboardHeight) { + container.setPadding(0, 0, 0, keyboardHeight); + } + } + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + if (container == null) { + container = (RelativeLayout) findViewById(R.id.container); + } + } + + public void showInternal(int keyboardHeight) { + showInternal = true; + this.keyboardHeight = keyboardHeight; + } + + public void dismissInternal() { + showInternal = false; + } + +} diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/inputbar/InputBarFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/inputbar/InputBarFragment.java index 849de3bc67..45c1f9cab3 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/inputbar/InputBarFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/inputbar/InputBarFragment.java @@ -4,6 +4,7 @@ import android.animation.ObjectAnimator; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; +import android.content.res.Configuration; import android.os.Build; import android.os.Bundle; import android.support.annotation.Nullable; @@ -192,8 +193,8 @@ public void afterTextChanged(Editable editable) { // Emoji keyboard // emojiButton = (ImageView) res.findViewById(R.id.ib_emoji); - emojiButton.setOnClickListener(v -> emojiKeyboard.toggle(messageEditText)); - emojiKeyboard = new EmojiKeyboard(getActivity()); + emojiButton.setOnClickListener(v -> emojiKeyboard.toggle()); + emojiKeyboard = new EmojiKeyboard(getActivity(), messageEditText); emojiKeyboard.setOnStickerClickListener(sticker -> { Fragment parent = getParentFragment(); if (parent instanceof InputBarCallback) { @@ -597,4 +598,17 @@ public void onDestroyView() { emojiKeyboard.release(); emojiKeyboard = null; } + + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + if (emojiKeyboard != null) { + emojiKeyboard.onConfigurationChange(); + } + } + + public boolean onBackPressed() { + return emojiKeyboard.onBackPressed(); + } } \ No newline at end of file diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/emoji/keyboard/BaseKeyboard.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/emoji/keyboard/BaseKeyboard.java index b9364a3ab8..4de9abca40 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/emoji/keyboard/BaseKeyboard.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/emoji/keyboard/BaseKeyboard.java @@ -2,23 +2,24 @@ import android.app.Activity; import android.content.Context; -import android.content.Intent; import android.content.res.Configuration; import android.graphics.PixelFormat; import android.graphics.Rect; -import android.net.Uri; -import android.os.Build; -import android.provider.Settings; import android.view.Gravity; import android.view.View; +import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.view.WindowManager; import android.view.inputmethod.InputMethodManager; import android.widget.EditText; +import android.widget.FrameLayout; +import android.widget.RelativeLayout; import android.widget.TextView; import im.actor.sdk.R; import im.actor.runtime.Log; +import im.actor.sdk.controllers.conversation.KeyboardLayout; +import im.actor.sdk.util.KeyboardHelper; public class BaseKeyboard implements ViewTreeObserver.OnGlobalLayoutListener { @@ -28,10 +29,13 @@ public class BaseKeyboard implements protected Activity activity; private View decorView; private boolean softKeyboardListeningEnabled = true; + private boolean showRequested = false; private boolean emojiKeyboardIsOpening; private InputMethodManager inputMethodManager; private View emojiKeyboardView; protected EditText messageBody; + protected KeyboardLayout root; + protected RelativeLayout container; public static final int OVERLAY_PERMISSION_REQ_CODE = 735; Boolean pendingOpen = false; @@ -42,11 +46,13 @@ public class BaseKeyboard implements int keyboardHeight = 0; private boolean showingPending; - private boolean showing; - private boolean dismissed; + private boolean showing = false; + // private boolean dismissed; private boolean softwareKeyboardShowing; + private KeyboardHelper keyboardHelper; + private boolean configChanged = false; - public BaseKeyboard(Activity activity) { + public BaseKeyboard(Activity activity, EditText messageBody) { this.activity = activity; this.windowManager = activity.getWindowManager(); this.inputMethodManager = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE); @@ -55,6 +61,8 @@ public BaseKeyboard(Activity activity) { //setSoftInputMode(LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); //default size keyboardHeight = (int) activity.getResources().getDimension(R.dimen.keyboard_height); + keyboardHelper = new KeyboardHelper(activity); + this.messageBody = messageBody; } @@ -67,88 +75,50 @@ public void setKeyboardStatusListener(KeyboardStatusListener keyboardStatusListe } - public void show(EditText messageBody) { - this.messageBody = messageBody; + public void show() { + messageBody.setOnClickListener(view -> { + if (showing) { + dismiss(); + } + }); + messageBody.setOnFocusChangeListener((view, b) -> { + if (b && showing) { + dismiss(); + } + }); + this.root = (KeyboardLayout) messageBody.getRootView().findViewById(R.id.container).getParent(); + this.container = (RelativeLayout) messageBody.getRootView().findViewById(R.id.container); - showing = true; - dismissed = false; + root.showInternal(keyboardHeight); + showRequested = true; if (softwareKeyboardShowing) { - showInternal(); + keyboardHelper.setImeVisibility(messageBody, false); } else { - messageBody.setFocusableInTouchMode(true); - messageBody.requestFocus(); - inputMethodManager.showSoftInput(messageBody, InputMethodManager.SHOW_IMPLICIT); +// messageBody.setFocusableInTouchMode(true); +// messageBody.requestFocus(); +// inputMethodManager.showSoftInput(messageBody, InputMethodManager.SHOW_IMPLICIT); + container.setPadding(0, 0, 0, keyboardHeight); + showInternal(); } } - private void showInternal() { - //Check - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - if (!Settings.canDrawOverlays(activity)) { - Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, - Uri.parse("package:" + activity.getPackageName())); - activity.startActivityForResult(intent, OVERLAY_PERMISSION_REQ_CODE); - } else { - showChecked(); - } - } else { - showChecked(); + public void showInternal() { + if (isShowing()) { + return; } - } + showRequested = false; + showing = true; - public void showChecked() { - if (showing == (emojiKeyboardView != null)) { - return; - } emojiKeyboardView = createView(); - WindowManager.LayoutParams params = new WindowManager.LayoutParams( - WindowManager.LayoutParams.MATCH_PARENT, - (keyboardHeight), - WindowManager.LayoutParams.TYPE_PHONE, - WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, - PixelFormat.TRANSLUCENT); - - params.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL; - windowManager.addView(emojiKeyboardView, params); -// emojiKeyboardView.post(new Runnable() { -// @Override -// public void run() { -// AlphaAnimation animation = new AlphaAnimation(0, 1); -// animation.setDuration(400); -// animation.setInterpolator(new MaterialInterpolator()); -// animation.setStartOffset(0); -// animation.setAnimationListener(new Animation.AnimationListener() { -// @Override -// public void onAnimationStart(Animation animation) { -// Log.d(TAG, "onAnimationStart"); -// } -// -// @Override -// public void onAnimationEnd(Animation animation) { -// Log.d(TAG, "onAnimationEnd"); -// } -// -// @Override -// public void onAnimationRepeat(Animation animation) { -// Log.d(TAG, "onAnimationReset"); -// } -// }); -// } -// }); - -// emojiKeyboardView.setTranslationY(140); -// emojiKeyboardView -// .animate() -// .y(0) -// .setDuration(200) -// .setStartDelay(0) -// .setInterpolator(new DecelerateInterpolator(1.4f)) -// .start(); - - if (keyboardStatusListener != null) + FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, keyboardHeight); + params.gravity = Gravity.BOTTOM; + root.addView(emojiKeyboardView, params); + + if (keyboardStatusListener != null) { keyboardStatusListener.onShow(); + } onShow(); } @@ -168,13 +138,19 @@ private void update() { } public void dismiss() { - dismissed = true; - showing = false; - dismissInternally(); + dismissInternally(false); } - private void dismissInternally() { - if (dismissed && emojiKeyboardView != null) { + public void dismiss(boolean all) { + dismissInternally(all); + } + + private void dismissInternally(boolean dismissAll) { + showing = false; + if (messageBody != null) { + keyboardHelper.setImeVisibility(messageBody, !dismissAll); + } + if (emojiKeyboardView != null && root != null && container != null && keyboardHelper != null) { final View emojiKeyboardViewCopy = emojiKeyboardView; // emojiKeyboardView // .animate() @@ -192,34 +168,41 @@ private void dismissInternally() { // }) // .start(); emojiKeyboardViewCopy.setVisibility(View.GONE); - windowManager.removeView(emojiKeyboardViewCopy); - showing = false; + root.removeView(emojiKeyboardViewCopy); emojiKeyboardView = null; - if (keyboardStatusListener != null) + if (keyboardStatusListener != null) { keyboardStatusListener.onDismiss(); + } + if (dismissAll) { + container.setPadding(0, 0, 0, 0); + } onDismiss(); } + + if (root != null) { + root.dismissInternal(); + } } - public void toggle(EditText messageBody) { + public void toggle() { if (isShowing()) { dismiss(); } else { - show(messageBody); + show(); } } public boolean isShowing() { - return emojiKeyboardView != null; + return showing && emojiKeyboardView != null; } public void destroy() { showing = false; - dismissed = true; +// dismissed = true; if (emojiKeyboardView != null) { - windowManager.removeView(emojiKeyboardView); +// windowManager.removeView(emojiKeyboardView); emojiKeyboardView = null; } if (keyboardStatusListener != null) { @@ -229,6 +212,7 @@ public void destroy() { @Override public void onGlobalLayout() { + Log.d(TAG, "onGlobalLayout"); if (!softKeyboardListeningEnabled) { return; @@ -240,6 +224,11 @@ public void onGlobalLayout() { .getHeight(); int heightDifference = screenHeight - (r.bottom - r.top); + + int widthDiff = decorView.getRootView().getWidth() - (r.right - r.left); + if (Math.abs(widthDiff) > 0) { + return; + } int resourceId = activity.getResources() .getIdentifier("status_bar_height", "dimen", "android"); @@ -264,21 +253,36 @@ public void onGlobalLayout() { } if (heightDifference > 100) { - Log.d(TAG, "onGlobalLayout: " + heightDifference); + softwareKeyboardShowing = true; + + Log.d(TAG, "onGlobalLayout: " + heightDifference); keyboardHeight = heightDifference; - Log.d(TAG, "onGlobalLayout: " + "showing"); + if (!showRequested) { + Log.d(TAG, "onGlobalLayout: " + "showing"); + + if (showing) { + dismiss(); + } + } - showInternal(); } else { + + softwareKeyboardShowing = false; + + if (showRequested) { + showInternal(); + } Log.d(TAG, "onGlobalLayout: " + heightDifference); Log.d(TAG, "onGlobalLayout: " + "dismiss?"); // dismiss not wirk - softwareKeyboardShowing = false; +// softwareKeyboardShowing = false; // keyboard showing or not? - dismissed = true; - dismissInternally(); +// dismissed = true; +// dismissInternally(); } + + } public Activity getActivity() { @@ -301,4 +305,17 @@ protected View createView() { return view; } + + public void onConfigurationChange() { + dismiss(true); + softwareKeyboardShowing = false; + } + + public boolean onBackPressed() { + if (showing) { + dismiss(true); + return true; + } + return false; + } } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/emoji/keyboard/emoji/EmojiKeyboard.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/emoji/keyboard/emoji/EmojiKeyboard.java index 0e10cfaf04..ccfb642faf 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/emoji/keyboard/emoji/EmojiKeyboard.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/emoji/keyboard/emoji/EmojiKeyboard.java @@ -23,6 +23,7 @@ import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; +import android.widget.EditText; import android.widget.FrameLayout; import android.widget.LinearLayout; @@ -54,8 +55,8 @@ public class EmojiKeyboard extends BaseKeyboard implements OnSmileClickListener, private SmilePagerAdapter mEmojisAdapter; - public EmojiKeyboard(Activity activity) { - super(activity); + public EmojiKeyboard(Activity activity, EditText messageBody) { + super(activity, messageBody); } @Override diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_chat.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_chat.xml index 4b8605e331..367533acbb 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_chat.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_chat.xml @@ -1,68 +1,73 @@ - - + android:layout_height="match_parent"> - - - + - + - + - + + android:layout_height="wrap_content" + android:layout_above="@+id/sendContainer" + android:visibility="gone" /> + android:layout_height="wrap_content" + android:layout_alignParentBottom="true"> - + + - - + android:visibility="gone"> - \ No newline at end of file + + + + + From 233b4e297d2174c868a88b240a9acbc4a93428fc Mon Sep 17 00:00:00 2001 From: kor_ka Date: Tue, 13 Sep 2016 23:52:11 +0300 Subject: [PATCH 343/414] ref(android): emoji keyboard under soft keyboard --- .../sdk/view/emoji/keyboard/BaseKeyboard.java | 41 +++++++++++-------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/emoji/keyboard/BaseKeyboard.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/emoji/keyboard/BaseKeyboard.java index 4de9abca40..c245a4a53a 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/emoji/keyboard/BaseKeyboard.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/emoji/keyboard/BaseKeyboard.java @@ -50,7 +50,7 @@ public class BaseKeyboard implements // private boolean dismissed; private boolean softwareKeyboardShowing; private KeyboardHelper keyboardHelper; - private boolean configChanged = false; + private boolean keyboardMeasured = false; public BaseKeyboard(Activity activity, EditText messageBody) { this.activity = activity; @@ -63,6 +63,18 @@ public BaseKeyboard(Activity activity, EditText messageBody) { keyboardHeight = (int) activity.getResources().getDimension(R.dimen.keyboard_height); keyboardHelper = new KeyboardHelper(activity); this.messageBody = messageBody; + + messageBody.setOnClickListener(view -> { + if (showing) { + dismiss(); + } + }); + messageBody.setOnFocusChangeListener((view, b) -> { + if (b && showing) { + dismiss(); + } + }); + } @@ -76,16 +88,7 @@ public void setKeyboardStatusListener(KeyboardStatusListener keyboardStatusListe public void show() { - messageBody.setOnClickListener(view -> { - if (showing) { - dismiss(); - } - }); - messageBody.setOnFocusChangeListener((view, b) -> { - if (b && showing) { - dismiss(); - } - }); + softKeyboardListeningEnabled = true; this.root = (KeyboardLayout) messageBody.getRootView().findViewById(R.id.container).getParent(); this.container = (RelativeLayout) messageBody.getRootView().findViewById(R.id.container); @@ -150,7 +153,7 @@ private void dismissInternally(boolean dismissAll) { if (messageBody != null) { keyboardHelper.setImeVisibility(messageBody, !dismissAll); } - if (emojiKeyboardView != null && root != null && container != null && keyboardHelper != null) { + if (emojiKeyboardView != null && root != null && keyboardHelper != null) { final View emojiKeyboardViewCopy = emojiKeyboardView; // emojiKeyboardView // .animate() @@ -225,10 +228,10 @@ public void onGlobalLayout() { int heightDifference = screenHeight - (r.bottom - r.top); - int widthDiff = decorView.getRootView().getWidth() - (r.right - r.left); - if (Math.abs(widthDiff) > 0) { - return; - } +// int widthDiff = decorView.getRootView().getWidth() - (r.right - r.left); +// if (Math.abs(widthDiff) > 0) { +// return; +// } int resourceId = activity.getResources() .getIdentifier("status_bar_height", "dimen", "android"); @@ -257,7 +260,9 @@ public void onGlobalLayout() { softwareKeyboardShowing = true; Log.d(TAG, "onGlobalLayout: " + heightDifference); + keyboardHeight = heightDifference; + if (!showRequested) { Log.d(TAG, "onGlobalLayout: " + "showing"); @@ -272,6 +277,10 @@ public void onGlobalLayout() { if (showRequested) { showInternal(); + } else { + if (root != null) { + root.dismissInternal(); + } } Log.d(TAG, "onGlobalLayout: " + heightDifference); Log.d(TAG, "onGlobalLayout: " + "dismiss?"); From 8d7afca30b5d300e2d12b30d507457e074de242f Mon Sep 17 00:00:00 2001 From: kor_ka Date: Wed, 14 Sep 2016 14:30:56 +0300 Subject: [PATCH 344/414] fix(android): dismiss emoji keyboard on pause --- .../im/actor/sdk/view/emoji/keyboard/BaseKeyboard.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/emoji/keyboard/BaseKeyboard.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/emoji/keyboard/BaseKeyboard.java index c245a4a53a..1ba03c2832 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/emoji/keyboard/BaseKeyboard.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/emoji/keyboard/BaseKeyboard.java @@ -202,12 +202,8 @@ public boolean isShowing() { public void destroy() { - showing = false; -// dismissed = true; - if (emojiKeyboardView != null) { -// windowManager.removeView(emojiKeyboardView); - emojiKeyboardView = null; - } + dismiss(true); + if (keyboardStatusListener != null) { keyboardStatusListener.onDismiss(); } From a92526310ca3995fa358654d522c9601155b6c1e Mon Sep 17 00:00:00 2001 From: rockjam Date: Wed, 14 Sep 2016 15:04:11 +0300 Subject: [PATCH 345/414] chore(server): use sbt launch script --- actor-server/docker.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actor-server/docker.sh b/actor-server/docker.sh index eb11c2c23f..bd8e3c6edf 100755 --- a/actor-server/docker.sh +++ b/actor-server/docker.sh @@ -1,3 +1,3 @@ #! /bin/bash -sbt docker:stage && docker build --no-cache=true -f Dockerfile -t actor/server . +./sbt docker:stage && docker build --no-cache=true -f Dockerfile -t actor/server . From 40a4418046da062c45bebb91c48cb9678ea4f438 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Wed, 14 Sep 2016 15:19:27 +0300 Subject: [PATCH 346/414] fix(server): Fixing endpoint URL (with future compatible fields) --- actor-server/src/docker/var/lib/actor/conf/server.conf | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/actor-server/src/docker/var/lib/actor/conf/server.conf b/actor-server/src/docker/var/lib/actor/conf/server.conf index c4487fef2b..507ceefe68 100644 --- a/actor-server/src/docker/var/lib/actor/conf/server.conf +++ b/actor-server/src/docker/var/lib/actor/conf/server.conf @@ -28,6 +28,10 @@ services { } } +http { + base-uri = ${?ACTOR_API_ENDPOINT} +} + modules { files { adapter: "im.actor.server.file.local.LocalFileStorageAdapter" @@ -52,6 +56,9 @@ modules { } ] } + api { + endpoint = ${?ACTOR_API_ENDPOINT} + } } akka { From d71141f987b210612380be4ac8f2c648da667aa7 Mon Sep 17 00:00:00 2001 From: rockjam Date: Wed, 14 Sep 2016 21:39:44 +0300 Subject: [PATCH 347/414] fix(server): dialog extensions adjustments --- .../main/scala/im/actor/server/dialog/ActorDelivery.scala | 4 ++-- .../scala/im/actor/server/dialog/DeliveryExtension.scala | 3 ++- .../main/scala/im/actor/server/dialog/DialogExtension.scala | 4 ++-- .../main/scala/im/actor/extension/InternalExtensions.scala | 6 +----- 4 files changed, 7 insertions(+), 10 deletions(-) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/dialog/ActorDelivery.scala b/actor-server/actor-core/src/main/scala/im/actor/server/dialog/ActorDelivery.scala index 5433dd0acd..f58eaaa387 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/dialog/ActorDelivery.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/dialog/ActorDelivery.scala @@ -12,8 +12,8 @@ import im.actor.server.user.UserExtension import scala.concurrent.{ ExecutionContext, Future } //default extension -final class ActorDelivery()(implicit val system: ActorSystem) - extends DeliveryExtension +final class ActorDelivery(val system: ActorSystem) + extends DeliveryExtension(system, Array.emptyByteArray) with PushText with PeersImplicits { diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DeliveryExtension.scala b/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DeliveryExtension.scala index 3d48b5970e..2c0d842df6 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DeliveryExtension.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DeliveryExtension.scala @@ -1,12 +1,13 @@ package im.actor.server.dialog +import akka.actor.ActorSystem import im.actor.api.rpc.messaging.ApiMessage import im.actor.server.sequence.SeqState import im.actor.server.model.Peer import scala.concurrent.Future -trait DeliveryExtension { +abstract class DeliveryExtension(system: ActorSystem, extData: Array[Byte]) { def receiverDelivery( receiverUserId: Int, diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogExtension.scala b/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogExtension.scala index 7cc3885297..4a06fcf58c 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogExtension.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/dialog/DialogExtension.scala @@ -305,8 +305,8 @@ final class DialogExtensionImpl(system: ActorSystem) extends DialogExtension wit extensions match { case Seq() ⇒ log.debug("No delivery extensions, using default one") - new ActorDelivery() - case ext +: tail ⇒ + new ActorDelivery(system) + case ext +: _ ⇒ log.debug("Got extensions: {}", extensions) val idToName = InternalExtensions.extensions(InternalDialogExtensions) idToName.get(ext.id) flatMap { className ⇒ diff --git a/actor-server/actor-runtime/src/main/scala/im/actor/extension/InternalExtensions.scala b/actor-server/actor-runtime/src/main/scala/im/actor/extension/InternalExtensions.scala index 0eec01518c..c0ad8b8930 100644 --- a/actor-server/actor-runtime/src/main/scala/im/actor/extension/InternalExtensions.scala +++ b/actor-server/actor-runtime/src/main/scala/im/actor/extension/InternalExtensions.scala @@ -10,10 +10,6 @@ trait InternalExtension object InternalExtensions { - def getId(path: String, name: String) = { - ActorConfig.load().getInt(s"$path.$name.id") - } - def extensions(path: String): Map[Int, String] = { val extConfig = ActorConfig.load().getConfig(path) val extensionsKeys = extConfig.root.keys @@ -26,4 +22,4 @@ object InternalExtensions { val constructor = Class.forName(extensionFQN).getConstructors()(0) constructor.newInstance(system, data).asInstanceOf[T] } -} \ No newline at end of file +} From df08d5a8a4b063feab2317c766ec1fac86cfd473 Mon Sep 17 00:00:00 2001 From: rockjam Date: Wed, 14 Sep 2016 22:35:22 +0300 Subject: [PATCH 348/414] fix(server): database migration fix --- .../resources/sql/migration/V20160902182358__SchemaCleanup.sql | 1 - 1 file changed, 1 deletion(-) diff --git a/actor-server/actor-persist/src/main/resources/sql/migration/V20160902182358__SchemaCleanup.sql b/actor-server/actor-persist/src/main/resources/sql/migration/V20160902182358__SchemaCleanup.sql index d744afdbc4..a6879db3a5 100644 --- a/actor-server/actor-persist/src/main/resources/sql/migration/V20160902182358__SchemaCleanup.sql +++ b/actor-server/actor-persist/src/main/resources/sql/migration/V20160902182358__SchemaCleanup.sql @@ -1,4 +1,3 @@ -drop table departments; drop table llectro_devices; drop table llectro_interests; drop table llectro_users; From 54fd5d66b97b2a77eff99d21901cc2f99e06b6b4 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Thu, 15 Sep 2016 16:50:23 +0300 Subject: [PATCH 349/414] fix(android): fix loosing input bar on emoji tabs slide + workaround for "keyboard hide ofter orientation change" bug --- .../conversation/KeyboardLayout.java | 10 ++++ .../sdk/view/emoji/keyboard/BaseKeyboard.java | 49 +++++++++++++------ 2 files changed, 43 insertions(+), 16 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/KeyboardLayout.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/KeyboardLayout.java index 860b37f711..98744ee447 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/KeyboardLayout.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/KeyboardLayout.java @@ -9,6 +9,7 @@ public class KeyboardLayout extends FrameLayout { boolean showInternal = false; + boolean sync = false; private RelativeLayout container; private int keyboardHeight; @@ -35,6 +36,7 @@ protected void onLayout(boolean changed, int left, int top, int right, int botto if (container.getPaddingBottom() != 0) { container.setPadding(0, 0, 0, 0); } + sync = showInternal; } } @@ -45,8 +47,10 @@ protected void onLayout(boolean changed, int left, int top, int right, int botto if (container.getPaddingBottom() != keyboardHeight) { container.setPadding(0, 0, 0, keyboardHeight); } + sync = showInternal; } } + } @Override @@ -60,10 +64,16 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { public void showInternal(int keyboardHeight) { showInternal = true; this.keyboardHeight = keyboardHeight; + sync = false; } public void dismissInternal() { showInternal = false; + sync = true; + } + + public boolean isSync() { + return sync == showInternal; } } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/emoji/keyboard/BaseKeyboard.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/emoji/keyboard/BaseKeyboard.java index 1ba03c2832..05f65d5f59 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/emoji/keyboard/BaseKeyboard.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/emoji/keyboard/BaseKeyboard.java @@ -144,14 +144,21 @@ public void dismiss() { dismissInternally(false); } - public void dismiss(boolean all) { - dismissInternally(all); + public void dismiss(boolean dismissAll) { + dismissInternally(dismissAll); } private void dismissInternally(boolean dismissAll) { showing = false; if (messageBody != null) { - keyboardHelper.setImeVisibility(messageBody, !dismissAll); + if (dismissAll) { + keyboardHelper.setImeVisibility(messageBody, false); + } else if (!softwareKeyboardShowing) { + keyboardHelper.setImeVisibility(messageBody, true); + } + } + if (root != null) { + root.dismissInternal(); } if (emojiKeyboardView != null && root != null && keyboardHelper != null) { final View emojiKeyboardViewCopy = emojiKeyboardView; @@ -181,10 +188,6 @@ private void dismissInternally(boolean dismissAll) { } onDismiss(); } - - if (root != null) { - root.dismissInternal(); - } } @@ -251,6 +254,9 @@ public void onGlobalLayout() { } } + boolean changed = softwareKeyboardShowing; + + if (heightDifference > 100) { softwareKeyboardShowing = true; @@ -259,21 +265,17 @@ public void onGlobalLayout() { keyboardHeight = heightDifference; - if (!showRequested) { - Log.d(TAG, "onGlobalLayout: " + "showing"); + dismiss(); - if (showing) { - dismiss(); - } - } } else { softwareKeyboardShowing = false; if (showRequested) { + root.showInternal(keyboardHeight); showInternal(); - } else { + } else if (changed) { if (root != null) { root.dismissInternal(); } @@ -287,6 +289,21 @@ public void onGlobalLayout() { // dismissInternally(); } + changed = changed != softwareKeyboardShowing; + Log.d(TAG, "keyboard state change: " + changed); + + // FIXME verify root view applied new padding after keyboard state change + // workaround for [some of android versions] bug, when keyboard closing not causing relayout, or causing it with delay + if (changed && root != null) { + + root.postDelayed(() -> { + if (!root.isSync()) { + root.requestLayout(); + } + }, 30); + } + + } @@ -312,8 +329,8 @@ protected View createView() { } public void onConfigurationChange() { - dismiss(true); - softwareKeyboardShowing = false; +// dismiss(true); +// softwareKeyboardShowing = false; } public boolean onBackPressed() { From a19eda263b4f92ef1eadb783d2e71f05649859f2 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Fri, 16 Sep 2016 19:29:18 +0300 Subject: [PATCH 350/414] fix(android): clear chat toolbar menu before inflate --- .../controllers/conversation/toolbar/ChatToolbarFragment.java | 1 + 1 file changed, 1 insertion(+) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/toolbar/ChatToolbarFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/toolbar/ChatToolbarFragment.java index 0d9338537e..7646182437 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/toolbar/ChatToolbarFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/toolbar/ChatToolbarFragment.java @@ -223,6 +223,7 @@ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); // Inflating menu + menu.clear(); inflater.inflate(R.menu.chat_menu, menu); // Show menu for opening chat contact From 5addb07db12f586e98cc5a88c116d9839d60bb5a Mon Sep 17 00:00:00 2001 From: kor_ka Date: Fri, 16 Sep 2016 19:49:41 +0300 Subject: [PATCH 351/414] fix(core): update isCanCall group permission --- .../src/main/java/im/actor/core/viewmodel/GroupVM.java | 1 + 1 file changed, 1 insertion(+) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java index 2c9883fba6..7d2c77515e 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java @@ -610,6 +610,7 @@ protected void updateValues(@NotNull Group rawObj) { isChanged |= isCanClear.change(rawObj.isCanClear()); isChanged |= isCanViewInfo.change(rawObj.isCanViewInfo()); isChanged |= isCanJoin.change(rawObj.isCanJoin()); + isChanged |= isCanCall.change(rawObj.isCanCall()); if (isChanged) { notifyIfNeeded(); From 23ae0e5bc98d477727623d21dbad9a595b88ce1c Mon Sep 17 00:00:00 2001 From: rockjam Date: Mon, 19 Sep 2016 20:33:40 +0300 Subject: [PATCH 352/414] fix(server:sequence): increment counter in difference when groupped dialogs updated --- .../operations/DifferenceOperations.scala | 20 +++++++++++++++++-- .../api/rpc/service/SequenceServiceSpec.scala | 12 ++++++++++- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/sequence/operations/DifferenceOperations.scala b/actor-server/actor-core/src/main/scala/im/actor/server/sequence/operations/DifferenceOperations.scala index b1a843ee10..c1e0271c0d 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/sequence/operations/DifferenceOperations.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/sequence/operations/DifferenceOperations.scala @@ -1,8 +1,7 @@ package im.actor.server.sequence.operations import akka.http.scaladsl.util.FastFuture -import im.actor.api.rpc.messaging.{ ApiDialogGroup, ApiDialogShort, UpdateChatGroupsChanged, UpdateMessageReadByMe } -import im.actor.api.rpc.peers.ApiPeer +import im.actor.api.rpc.messaging._ import im.actor.api.rpc.sequence.UpdateEmptyUpdate import im.actor.server.dialog.{ DialogGroupKeys, DialogGroupTitles } import im.actor.server.model.{ SeqUpdate, SerializedUpdate, UpdateMapping } @@ -59,6 +58,15 @@ trait DifferenceOperations { this: SeqUpdatesExtension ⇒ } } + private def incrementCounter(dialogs: IndexedSeq[ApiDialogShort], upd: UpdateMessage) = + dialogs map { dlg ⇒ + if (upd.peer == dlg.peer) { + dlg.copy(counter = dlg.counter + 1) + } else { + dlg + } + } + def toVector = { val originalUpdates = (generic ++ reduced.values).values.toVector @@ -96,6 +104,14 @@ trait DifferenceOperations { this: SeqUpdatesExtension ⇒ rewriteDialogsCounter(dir, upd) ) } getOrElse acc + } else if (upd.header == upd.header) { + UpdateMessage.parseFrom(upd.body).right.toOption map { upd ⇒ + ( + incrementCounter(fav, upd), + incrementCounter(gr, upd), + incrementCounter(dir, upd) + ) + } getOrElse acc } else acc } diff --git a/actor-server/actor-tests/src/test/scala/im/actor/server/api/rpc/service/SequenceServiceSpec.scala b/actor-server/actor-tests/src/test/scala/im/actor/server/api/rpc/service/SequenceServiceSpec.scala index 6f0e15d1dd..652b384df1 100644 --- a/actor-server/actor-tests/src/test/scala/im/actor/server/api/rpc/service/SequenceServiceSpec.scala +++ b/actor-server/actor-tests/src/test/scala/im/actor/server/api/rpc/service/SequenceServiceSpec.scala @@ -307,12 +307,15 @@ final class SequenceServiceSpec extends BaseAppSuite({ { implicit val cd = aliceClientData + // FAVOURITE whenReady(msgService.handleFavouriteDialog(getOutPeer(bob.id, aliceAuthId)))(identity) // read 5 messages, 15 left whenReady(msgService.handleMessageRead(getOutPeer(bob.id, aliceAuthId), bobReadDates(4)))(identity) + // UNFAVOURITE whenReady(msgService.handleUnfavouriteDialog(getOutPeer(bob.id, aliceAuthId)))(identity) + // read 10 messages, 10 left whenReady(msgService.handleMessageRead(getOutPeer(bob.id, aliceAuthId), bobReadDates(9)))(identity) @@ -320,6 +323,13 @@ final class SequenceServiceSpec extends BaseAppSuite({ whenReady(msgService.handleMessageRead(group.asOutPeer, groupReadDates.last))(identity) } + { + implicit val cd = ClientData(bobAuthId, sessionId, Some(AuthData(bob.id, bobAuthSid, 42))) + 1 to 10 map { i ⇒ + sendMessageToUser(alice.id, s"Hello $i")._2.date + } + } + { implicit val cd = aliceClientData @@ -336,7 +346,7 @@ final class SequenceServiceSpec extends BaseAppSuite({ optDirect shouldBe defined val bobsDialog = optDirect.get.dialogs.find(_.peer.id == bob.id) - bobsDialog.get.counter shouldEqual 10 + bobsDialog.get.counter shouldEqual 20 val optGroups = upd.dialogs.find(_.key == DialogGroupKeys.Groups) optGroups shouldBe defined From de286c13a886c79a4e56516017a20c4ba20d967a Mon Sep 17 00:00:00 2001 From: rockjam Date: Mon, 19 Sep 2016 21:23:06 +0300 Subject: [PATCH 353/414] fix(server: sequence): new messages in groupped dialog update --- .../operations/DifferenceOperations.scala | 2 +- .../api/rpc/service/SequenceServiceSpec.scala | 59 ++++++++++++++++++- 2 files changed, 58 insertions(+), 3 deletions(-) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/sequence/operations/DifferenceOperations.scala b/actor-server/actor-core/src/main/scala/im/actor/server/sequence/operations/DifferenceOperations.scala index c1e0271c0d..e89d7d457d 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/sequence/operations/DifferenceOperations.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/sequence/operations/DifferenceOperations.scala @@ -60,7 +60,7 @@ trait DifferenceOperations { this: SeqUpdatesExtension ⇒ private def incrementCounter(dialogs: IndexedSeq[ApiDialogShort], upd: UpdateMessage) = dialogs map { dlg ⇒ - if (upd.peer == dlg.peer) { + if (upd.peer == dlg.peer && upd.date > dlg.date) { dlg.copy(counter = dlg.counter + 1) } else { dlg diff --git a/actor-server/actor-tests/src/test/scala/im/actor/server/api/rpc/service/SequenceServiceSpec.scala b/actor-server/actor-tests/src/test/scala/im/actor/server/api/rpc/service/SequenceServiceSpec.scala index 652b384df1..b7f3e0ec7a 100644 --- a/actor-server/actor-tests/src/test/scala/im/actor/server/api/rpc/service/SequenceServiceSpec.scala +++ b/actor-server/actor-tests/src/test/scala/im/actor/server/api/rpc/service/SequenceServiceSpec.scala @@ -38,7 +38,8 @@ final class SequenceServiceSpec extends BaseAppSuite({ it should "not produce empty difference if there is one update bigger than difference size limit" in bigUpdate it should "map updates correctly" in mapUpdates it should "exclude optimized updates from sequence" in optimizedUpdates - it should "return single UpdateChatGroupsChanged in difference as if it was applied after all message reads" in chatGroupChanged + it should "return single UpdateChatGroupsChanged in difference as if it was applied after all message reads" in chatGroupChangedRead + it should "return single UpdateChatGroupsChanged in difference as if it was aplied after all received messages" in chatGroupChangedReceived private val config = SequenceServiceConfig.load().get @@ -278,7 +279,7 @@ final class SequenceServiceSpec extends BaseAppSuite({ } } - def chatGroupChanged(): Unit = { + def chatGroupChangedRead(): Unit = { val (alice, aliceAuthId, aliceAuthSid, _) = createUser() val sessionId = createSessionId() @@ -358,4 +359,58 @@ final class SequenceServiceSpec extends BaseAppSuite({ } + def chatGroupChangedReceived(): Unit = { + val (alice, aliceAuthId, aliceAuthSid, _) = createUser() + + val sessionId = createSessionId() + val aliceClientData = ClientData(aliceAuthId, sessionId, Some(AuthData(alice.id, aliceAuthSid, 42))) + + val (bob, bobAuthId, bobAuthSid, _) = createUser() + val bobClientData = ClientData(bobAuthId, sessionId, Some(AuthData(bob.id, bobAuthSid, 42))) + + { + implicit val cd = bobClientData + 1 to 10 map { i ⇒ + sendMessageToUser(alice.id, s"Hello $i")._2.date + } + } + + { + implicit val cd = aliceClientData + // FAVOURITE + whenReady(msgService.handleFavouriteDialog(getOutPeer(bob.id, aliceAuthId)))(identity) + + // UNFAVOURITE + whenReady(msgService.handleUnfavouriteDialog(getOutPeer(bob.id, aliceAuthId)))(identity) + } + + { + implicit val cd = bobClientData + 1 to 5 map { i ⇒ + sendMessageToUser(alice.id, s"Hello $i")._2.date + } + } + + { + implicit val cd = aliceClientData + + whenReady(service.handleGetDifference(0, Array.empty, Vector.empty)) { res ⇒ + inside(res) { + case Ok(rsp: ResponseGetDifference) ⇒ + val groupedChatsUpdates = rsp.updates.filter(_.updateHeader == UpdateChatGroupsChanged.header) + groupedChatsUpdates should have length 1 + } + } + + expectUpdate(classOf[UpdateChatGroupsChanged]) { upd ⇒ + val optDirect = upd.dialogs.find(_.key == DialogGroupKeys.Direct) + optDirect shouldBe defined + + val bobsDialog = optDirect.get.dialogs.find(_.peer.id == bob.id) + bobsDialog.get.counter shouldEqual 15 + } + } + + } + } From b76f0827a1e1eebfb23225cc01ea804031c3c62a Mon Sep 17 00:00:00 2001 From: rockjam Date: Wed, 21 Sep 2016 18:55:36 +0300 Subject: [PATCH 354/414] fix(server:files): make S3 adapter compatible with minio --- .../server/file/s3/S3StorageAdapter.scala | 34 +++++++++++-------- actor-server/project/Dependencies.scala | 4 +-- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/actor-server/actor-fs-adapters/src/main/scala/im/actor/server/file/s3/S3StorageAdapter.scala b/actor-server/actor-fs-adapters/src/main/scala/im/actor/server/file/s3/S3StorageAdapter.scala index b303532ef6..0d8ca2c5e8 100644 --- a/actor-server/actor-fs-adapters/src/main/scala/im/actor/server/file/s3/S3StorageAdapter.scala +++ b/actor-server/actor-fs-adapters/src/main/scala/im/actor/server/file/s3/S3StorageAdapter.scala @@ -33,26 +33,31 @@ final class S3StorageAdapter(_system: ActorSystem) extends FileStorageAdapter { private implicit val ec: ExecutionContext = system.dispatcher private implicit val mat: Materializer = ActorMaterializer() + private val db = DbExtension(system).db + private val config = S3StorageAdapterConfig.load private val bucketName = config.bucketName private val awsCredentials = new BasicAWSCredentials(config.key, config.secret) - private val db = DbExtension(system).db - val s3Client = { - val blueprint = new AmazonS3ScalaClient(awsCredentials) - config.endpoint foreach { endpoint ⇒ - blueprint.client.setEndpoint(endpoint) - } + private val s3Client = { + val cl = new AmazonS3ScalaClient(awsCredentials) config.region foreach { region ⇒ - blueprint.client.setRegion(Region.getRegion(Regions.fromName(region))) + cl.client.setRegion(Region.getRegion(Regions.fromName(region))) + } + config.endpoint foreach { endpoint ⇒ + cl.client.setEndpoint(endpoint) } if (config.pathStyleAccess) { - blueprint.client.setS3ClientOptions(new S3ClientOptions().withPathStyleAccess(true)) + val co = S3ClientOptions.builder() + .setPathStyleAccess(true) + .build() + cl.client.setS3ClientOptions(co) } - blueprint + System.setProperty("com.amazonaws.services.s3.disableGetObjectMD5Validation", "true") + cl } - val transferManager = new TransferManager(s3Client.client) + private val transferManager = new TransferManager(s3Client.client) override def uploadFile(name: UnsafeFileName, data: Array[Byte]): DBIO[FileLocation] = uploadFile(bucketName, name, data) @@ -122,7 +127,6 @@ final class S3StorageAdapter(_system: ActorSystem) extends FileStorageAdapter { expiration.setTime(expiration.getTime + 1.day.toMillis) request.setMethod(HttpMethod.PUT) request.setExpiration(expiration) - request.setContentType("application/octet-stream") for (url ← s3Client.generatePresignedUrlRequest(request)) yield partKey → url.toString } @@ -141,10 +145,10 @@ final class S3StorageAdapter(_system: ActorSystem) extends FileStorageAdapter { override def completeFileUpload(fileId: Long, fileSize: Long, fileName: UnsafeFileName, partNames: Seq[String]): Future[Unit] = { for { tempDir ← createTempDir() - fk = uploadKey(fileId).key - _ ← FutureTransfer.listenFor { - transferManager.downloadDirectory(bucketName, s"upload_part_$fk", tempDir) - } map (_.waitForCompletion()) + _ ← Future.sequence(partNames map { part ⇒ + val path = tempDir.toPath.resolve(part) + FutureTransfer.listenFor(transferManager.download(bucketName, part, path.toFile)).map(_.waitForCompletion()) + }) concatFile ← concatFiles(tempDir, partNames) _ ← FutureTransfer.listenFor { transferManager.upload(bucketName, s3Key(fileId, fileName.safe), concatFile) diff --git a/actor-server/project/Dependencies.scala b/actor-server/project/Dependencies.scala index 85761fdeed..37c6c11eb3 100644 --- a/actor-server/project/Dependencies.scala +++ b/actor-server/project/Dependencies.scala @@ -72,8 +72,8 @@ object Dependencies { val flywayCore = "org.flywaydb" % "flyway-core" % "3.1" val hikariCP = "com.zaxxer" % "HikariCP" % "2.4.6" - val amazonaws = "com.amazonaws" % "aws-java-sdk-s3" % "1.9.31" - val awsWrap = "com.github.dwhjames" %% "aws-wrap" % "0.7.2" + val amazonaws = "com.amazonaws" % "aws-java-sdk-s3" % "1.11.32" + val awsWrap = "com.github.dwhjames" %% "aws-wrap" % "0.8.0" val bcprov = "org.bouncycastle" % "bcprov-jdk15on" % "1.50" From 6585db033604e61cca7110beff5a80ce00b98882 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Wed, 21 Sep 2016 19:14:24 +0300 Subject: [PATCH 355/414] chore(android): bump google services --- .../android-google-maps/build.gradle | 2 +- .../main/java/im/actor/map/MapPickerActivity.java | 14 ++++++++------ .../android-google-push/build.gradle | 2 +- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-google-maps/build.gradle b/actor-sdk/sdk-core-android/android-google-maps/build.gradle index 88ae171a0a..30887aa5a4 100644 --- a/actor-sdk/sdk-core-android/android-google-maps/build.gradle +++ b/actor-sdk/sdk-core-android/android-google-maps/build.gradle @@ -52,7 +52,7 @@ dependencies { //compile 'im.actor:android-sdk:0.1.30' compile project(':actor-sdk:sdk-core-android:android-sdk') - compile 'com.google.android.gms:play-services-maps:8.4.0' + compile 'com.google.android.gms:play-services-maps:9.4.0' } diff --git a/actor-sdk/sdk-core-android/android-google-maps/src/main/java/im/actor/map/MapPickerActivity.java b/actor-sdk/sdk-core-android/android-google-maps/src/main/java/im/actor/map/MapPickerActivity.java index 0772c7522b..12324ee953 100644 --- a/actor-sdk/sdk-core-android/android-google-maps/src/main/java/im/actor/map/MapPickerActivity.java +++ b/actor-sdk/sdk-core-android/android-google-maps/src/main/java/im/actor/map/MapPickerActivity.java @@ -290,12 +290,14 @@ private void setUpMapIfNeeded() { // Do a null check to confirm that we have not already instantiated the map. if (mMap == null) { // Try to obtain the map from the SupportMapFragment. - mMap = ((MapFragment) getFragmentManager().findFragmentById(R.id.map)) - .getMap(); - // Check if we were successful in obtaining the map. - if (mMap != null) { - setUpMap(); - } + ((MapFragment) getFragmentManager().findFragmentById(R.id.map)) + .getMapAsync(googleMap -> { + mMap = googleMap; + if (mMap != null) { + setUpMap(); + } + }); + } } diff --git a/actor-sdk/sdk-core-android/android-google-push/build.gradle b/actor-sdk/sdk-core-android/android-google-push/build.gradle index 499980ac4a..762bbb94f5 100644 --- a/actor-sdk/sdk-core-android/android-google-push/build.gradle +++ b/actor-sdk/sdk-core-android/android-google-push/build.gradle @@ -50,7 +50,7 @@ dependencies { //compile 'im.actor:android-sdk:0.1.30' compile project(':actor-sdk:sdk-core-android:android-sdk') - compile 'com.google.android.gms:play-services-gcm:8.4.0' + compile 'com.google.android.gms:play-services-gcm:9.4.0' } // From e8a06f4dd89d657ae9164b258b35d0b3399014ce Mon Sep 17 00:00:00 2001 From: kor_ka Date: Wed, 21 Sep 2016 20:24:58 +0300 Subject: [PATCH 356/414] chore(android): wrap json message view holder if needed --- .../conversation/messages/JsonXmlBubbleLayouter.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/JsonXmlBubbleLayouter.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/JsonXmlBubbleLayouter.java index 3e068d9cf2..ccf2e063f5 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/JsonXmlBubbleLayouter.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/JsonXmlBubbleLayouter.java @@ -25,7 +25,14 @@ public JsonXmlBubbleLayouter(String dataType, @LayoutRes int id, @NotNull ViewHo @Override public AbsMessageViewHolder onCreateViewHolder(MessagesAdapter adapter, ViewGroup root, Peer peer) { - return creator.onCreateViewHolder(adapter, (ViewGroup) ViewUtils.inflate(id, root), peer); + ViewGroup holder = (ViewGroup) ViewUtils.inflate(id, root); + if (!(holder instanceof BubbleContainer)) { + BubbleContainer rootHolder = new BubbleContainer(root.getContext()); + rootHolder.addView(holder, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); + holder = rootHolder; + holder.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + } + return creator.onCreateViewHolder(adapter, holder, peer); } } From 50c3e85db3994e95d71f39ec02e114ea0563481d Mon Sep 17 00:00:00 2001 From: kor_ka Date: Wed, 21 Sep 2016 20:47:38 +0300 Subject: [PATCH 357/414] chore(android): bump play services --- actor-sdk/sdk-core-android/android-google-maps/build.gradle | 1 + actor-sdk/sdk-core-android/android-google-push/build.gradle | 1 + 2 files changed, 2 insertions(+) diff --git a/actor-sdk/sdk-core-android/android-google-maps/build.gradle b/actor-sdk/sdk-core-android/android-google-maps/build.gradle index 30887aa5a4..c2ff47223d 100644 --- a/actor-sdk/sdk-core-android/android-google-maps/build.gradle +++ b/actor-sdk/sdk-core-android/android-google-maps/build.gradle @@ -4,6 +4,7 @@ buildscript { classpath 'com.github.dcendents:android-maven-plugin:1.2' classpath "io.codearte.gradle.nexus:gradle-nexus-staging-plugin:0.5.3" classpath 'me.tatarka:gradle-retrolambda:3.2.5' + classpath "com.google.gms:google-services:3.0.0" } } plugins { diff --git a/actor-sdk/sdk-core-android/android-google-push/build.gradle b/actor-sdk/sdk-core-android/android-google-push/build.gradle index 762bbb94f5..c00020cbcf 100644 --- a/actor-sdk/sdk-core-android/android-google-push/build.gradle +++ b/actor-sdk/sdk-core-android/android-google-push/build.gradle @@ -4,6 +4,7 @@ buildscript { classpath 'com.github.dcendents:android-maven-plugin:1.2' classpath "io.codearte.gradle.nexus:gradle-nexus-staging-plugin:0.5.3" classpath 'me.tatarka:gradle-retrolambda:3.2.5' + classpath "com.google.gms:google-services:3.0.0" } } From 27713786652b9e0568a7dc2f3c85845b26754a8d Mon Sep 17 00:00:00 2001 From: kor_ka Date: Wed, 21 Sep 2016 20:49:40 +0300 Subject: [PATCH 358/414] chore(android): bump play services --- actor-sdk/sdk-core-android/android-app/build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/actor-sdk/sdk-core-android/android-app/build.gradle b/actor-sdk/sdk-core-android/android-app/build.gradle index c013ad78f6..64f4e5c3ce 100644 --- a/actor-sdk/sdk-core-android/android-app/build.gradle +++ b/actor-sdk/sdk-core-android/android-app/build.gradle @@ -2,6 +2,7 @@ buildscript { dependencies { classpath 'com.android.tools.build:gradle:2.1.3' classpath 'me.tatarka:gradle-retrolambda:3.2.5' + classpath "com.google.gms:google-services:3.0.0" } } From cd47909e5394b0ada8a36aa01b51ded08b6c5a1f Mon Sep 17 00:00:00 2001 From: rockjam Date: Wed, 21 Sep 2016 21:20:50 +0300 Subject: [PATCH 359/414] fix(server:s3): minio work with https --- .../main/scala/im/actor/server/file/s3/S3StorageAdapter.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/actor-server/actor-fs-adapters/src/main/scala/im/actor/server/file/s3/S3StorageAdapter.scala b/actor-server/actor-fs-adapters/src/main/scala/im/actor/server/file/s3/S3StorageAdapter.scala index 0d8ca2c5e8..93773a3670 100644 --- a/actor-server/actor-fs-adapters/src/main/scala/im/actor/server/file/s3/S3StorageAdapter.scala +++ b/actor-server/actor-fs-adapters/src/main/scala/im/actor/server/file/s3/S3StorageAdapter.scala @@ -50,6 +50,7 @@ final class S3StorageAdapter(_system: ActorSystem) extends FileStorageAdapter { if (config.pathStyleAccess) { val co = S3ClientOptions.builder() .setPathStyleAccess(true) + .setPayloadSigningEnabled(true) .build() cl.client.setS3ClientOptions(co) } From 8a5b5f433052fd224469abac249f6c68334128d6 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Thu, 22 Sep 2016 16:51:01 +0300 Subject: [PATCH 360/414] feat(android): auto download settings --- .../messages/content/AudioHolder.java | 6 +- .../messages/content/DocHolder.java | 2 +- .../messages/content/PhotoHolder.java | 11 +- .../settings/ChatSettingsFragment.java | 39 +++-- .../src/main/res/layout/fr_settings_chat.xml | 161 ++++++++++++++++++ .../src/main/res/values-ru/ui_text.xml | 7 + .../src/main/res/values/ui_text.xml | 6 + .../main/java/im/actor/core/Messenger.java | 100 +++++++++++ .../core/modules/settings/SettingsModule.java | 53 ++++++ 9 files changed, 370 insertions(+), 15 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/AudioHolder.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/AudioHolder.java index 9798482604..a8d30c66fe 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/AudioHolder.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/AudioHolder.java @@ -38,6 +38,7 @@ import im.actor.sdk.core.audio.AudioPlayerActor; import im.actor.sdk.view.TintImageView; +import static im.actor.sdk.util.ActorSDKMessenger.messenger; import static im.actor.sdk.util.ActorSDKMessenger.myUid; import static im.actor.sdk.util.ViewUtils.goneView; import static im.actor.sdk.util.ViewUtils.showView; @@ -294,7 +295,10 @@ protected void bindData(final Message message, long readDate, long receiveDate, // Resetting progress state if (audioMsg.getSource() instanceof FileRemoteSource) { - boolean autoDownload = audioMsg instanceof VoiceContent; + boolean autoDownload = false; + if (audioMsg instanceof VoiceContent) { + autoDownload = messenger().isAudioAutoDownloadEnabled(); + } downloadFileVM = ActorSDK.sharedActor().getMessenger().bindFile(((FileRemoteSource) audioMsg.getSource()).getFileReference(), autoDownload, new DownloadVMCallback(audioMsg)); } else if (audioMsg.getSource() instanceof FileLocalSource) { diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/DocHolder.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/DocHolder.java index 75f8de60bf..58dc96925c 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/DocHolder.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/DocHolder.java @@ -298,7 +298,7 @@ protected void bindData(Message message, long readDate, long receiveDate, boolea if (document.getSource() instanceof FileRemoteSource) { FileRemoteSource remoteSource = (FileRemoteSource) document.getSource(); - boolean autoDownload = remoteSource.getFileReference().getFileSize() <= 1024 * 1024;// < 1MB + boolean autoDownload = remoteSource.getFileReference().getFileSize() <= 1024 * 1024 && messenger().isDocAutoDownloadEnabled();// < 1MB downloadFileVM = messenger().bindFile(remoteSource.getFileReference(), autoDownload, new DownloadVMCallback()); } else if (document.getSource() instanceof FileLocalSource) { diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/PhotoHolder.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/PhotoHolder.java index eeeb20c514..2f0197eb2f 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/PhotoHolder.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/content/PhotoHolder.java @@ -288,7 +288,16 @@ protected void bindData(Message message, long readDate, long receiveDate, boolea progressIcon.setVisibility(View.GONE); if (fileMessage.getSource() instanceof FileRemoteSource) { - boolean autoDownload = (fileMessage instanceof PhotoContent) || (fileMessage instanceof AnimationContent); + + boolean autoDownload = false; + if (fileMessage instanceof PhotoContent) { + autoDownload = messenger().isImageAutoDownloadEnabled(); + } else if (fileMessage instanceof AnimationContent) { + autoDownload = messenger().isAnimationAutoPlayEnabled(); + } else if (fileMessage instanceof VideoContent) { + autoDownload = messenger().isVideoAutoDownloadEnabled(); + } + if (!updated) { previewView.setImageURI(null); } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/ChatSettingsFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/ChatSettingsFragment.java index 554fcd6a5f..6fc0558496 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/ChatSettingsFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/ChatSettingsFragment.java @@ -36,20 +36,35 @@ public void onClick(View v) { ((TextView) res.findViewById(R.id.settings_send_by_enter_title)).setTextColor(ActorSDK.sharedActor().style.getTextPrimaryColor()); ((TextView) res.findViewById(R.id.settings_set_by_enter_hint)).setTextColor(ActorSDK.sharedActor().style.getTextSecondaryColor()); - final CheckBox animationsAtoPlay = (CheckBox) res.findViewById(R.id.animationAutoPlay); - animationsAtoPlay.setChecked(messenger().isAnimationAutoPlayEnabled()); - View.OnClickListener animListener = new View.OnClickListener() { - @Override - public void onClick(View v) { - messenger().changeAnimationAutoPlayEnabled(!messenger().isAnimationAutoPlayEnabled()); - animationsAtoPlay.setChecked(messenger().isAnimationAutoPlayEnabled()); - } - }; - animationsAtoPlay.setOnClickListener(animListener); - res.findViewById(R.id.animationAutoPlayCont).setOnClickListener(animListener); - ((TextView) res.findViewById(R.id.settings_animation_auto_play_title)).setTextColor(ActorSDK.sharedActor().style.getTextPrimaryColor()); + setupCheckbox(res, R.id.animationAutoPlay, R.id.animationAutoPlayCont, R.id.settings_animation_auto_play_title, () -> messenger().changeAnimationAutoPlayEnabled(!messenger().isAnimationAutoPlayEnabled()), () -> messenger().isAnimationAutoPlayEnabled()); ((TextView) res.findViewById(R.id.settings_animation_auto_play_hint)).setTextColor(ActorSDK.sharedActor().style.getTextSecondaryColor()); + setupCheckbox(res, R.id.animationAutoDownload, R.id.animationAutoDownloadCont, R.id.settings_animation_download_title, () -> messenger().changeAnimationAutoDownloadEnabled(!messenger().isAnimationAutoDownloadEnabled()), () -> messenger().isAnimationAutoDownloadEnabled()); + setupCheckbox(res, R.id.imageAutoDownload, R.id.imageAutoDownloadCont, R.id.settings_image_download_title, () -> messenger().changeImageAutoDownloadEnabled(!messenger().isImageAutoDownloadEnabled()), () -> messenger().isImageAutoDownloadEnabled()); + setupCheckbox(res, R.id.videoAutoDownload, R.id.videoAutoDownloadCont, R.id.settings_video_download_title, () -> messenger().changeVideoAutoDownloadEnabled(!messenger().isVideoAutoDownloadEnabled()), () -> messenger().isVideoAutoDownloadEnabled()); + setupCheckbox(res, R.id.audioAutoDownload, R.id.audioAutoDownloadCont, R.id.settings_audio_download_title, () -> messenger().changeAudioAutoDownloadEnabled(!messenger().isAudioAutoDownloadEnabled()), () -> messenger().isAudioAutoDownloadEnabled()); + setupCheckbox(res, R.id.docAutoDownload, R.id.docAutoDownloadCont, R.id.settings_doc_download_title, () -> messenger().changeDocAutoDownloadEnabled(!messenger().isDocAutoDownloadEnabled()), () -> messenger().isDocAutoDownloadEnabled()); + return res; } + + protected void setupCheckbox(View root, int chbId, int contId, int titleId, OnClLstnr lstnr, Checker checker) { + final CheckBox animationsAtoPlay = (CheckBox) root.findViewById(chbId); + animationsAtoPlay.setChecked(checker.check()); + View.OnClickListener animListener = v -> { + lstnr.onClick(); + animationsAtoPlay.setChecked(checker.check()); + }; + animationsAtoPlay.setOnClickListener(animListener); + root.findViewById(contId).setOnClickListener(animListener); + ((TextView) root.findViewById(titleId)).setTextColor(ActorSDK.sharedActor().style.getTextPrimaryColor()); + } + + private interface OnClLstnr { + void onClick(); + } + + private interface Checker { + boolean check(); + } } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fr_settings_chat.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fr_settings_chat.xml index 16cc2216c6..26733a58a9 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fr_settings_chat.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fr_settings_chat.xml @@ -107,6 +107,167 @@ android:gravity="center_vertical" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Использование разметки markdown + Автоматически загружать анимации + Автоматически загружать изобажения + Автоматически загружать видео + Автоматически загружать аудио + Автоматически загружать документы + + Безопасность и Приватность Уничтожить все сессии diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml index c7f99c741e..b6bd18a968 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml @@ -548,6 +548,12 @@ Markdown Is markdown enabled + Auto download animations + Auto download images + Auto download videos + Auto download audio + Auto download documents + Security and Privacy Terminate all sessions diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java index 75971c46af..dcf2003780 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java @@ -2439,6 +2439,106 @@ public void changeAnimationAutoPlayEnabled(boolean val) { modules.getSettingsModule().setAnimationAutoPlayEnabled(val); } + /** + * Is animation content auto download enabled + * + * @return is animation auto download enabled + */ + @ObjectiveCName("isAnimationAutoDownloadEnabled") + public boolean isAnimationAutoDownloadEnabled() { + return modules.getSettingsModule().isAnimationAutoDownloadEnabled(); + } + + /** + * Change animation auto download enabled + * + * @param val is auto download enabled + */ + @ObjectiveCName("changeAnimationAutoDownloadEnabled:") + public void changeAnimationAutoDownloadEnabled(boolean val) { + modules.getSettingsModule().setAnimationAutoDownloadEnabled(val); + } + + /** + * Is image content auto download enabled + * + * @return is image auto download enabled + */ + @ObjectiveCName("isImageAutoDownloadEnabled") + public boolean isImageAutoDownloadEnabled() { + return modules.getSettingsModule().isImageAutoDownloadEnabled(); + } + + /** + * Change image auto download enabled + * + * @param val is auto download enabled + */ + @ObjectiveCName("changeImageAutoDownloadEnabled:") + public void changeImageAutoDownloadEnabled(boolean val) { + modules.getSettingsModule().setImageAutoDownloadEnabled(val); + } + + /** + * Is video content auto download enabled + * + * @return is video auto download enabled + */ + @ObjectiveCName("isVideoAutoDownloadEnabled") + public boolean isVideoAutoDownloadEnabled() { + return modules.getSettingsModule().isVideoAutoDownloadEnabled(); + } + + /** + * Change video auto download enabled + * + * @param val is auto download enabled + */ + @ObjectiveCName("changeVideoAutoDownloadEnabled:") + public void changeVideoAutoDownloadEnabled(boolean val) { + modules.getSettingsModule().setVideoAutoDownloadEnabled(val); + } + + /** + * Is audio content auto download enabled + * + * @return is audio auto download enabled + */ + @ObjectiveCName("isAudioAutoDownloadEnabled") + public boolean isAudioAutoDownloadEnabled() { + return modules.getSettingsModule().isAudioAutoDownloadEnabled(); + } + + /** + * Change audio auto download enabled + * + * @param val is auto download enabled + */ + @ObjectiveCName("changeAudioAutoDownloadEnabled:") + public void changeAudioAutoDownloadEnabled(boolean val) { + modules.getSettingsModule().setAudioAutoDownloadEnabled(val); + } + + /** + * Is doc content auto download enabled + * + * @return is doc auto download enabled + */ + @ObjectiveCName("isDocAutoDownloadEnabled") + public boolean isDocAutoDownloadEnabled() { + return modules.getSettingsModule().isDocAutoDownloadEnabled(); + } + + /** + * Change doc auto download enabled + * + * @param val is auto download enabled + */ + @ObjectiveCName("changeDocAutoDownloadEnabled:") + public void changeDocAutoDownloadEnabled(boolean val) { + modules.getSettingsModule().setDocAutoDownloadEnabled(val); + } + ////////////////////////////////////// // Security diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/settings/SettingsModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/settings/SettingsModule.java index 6dfd60ec0d..b4b527620b 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/settings/SettingsModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/settings/SettingsModule.java @@ -43,6 +43,12 @@ public class SettingsModule extends AbsModule { private final String KEY_ANIMATION_AUTO_PLAY; + private final String KEY_DOC_AUTO_DOWNLOAD; + private final String KEY_IMAGE_AUTO_DOWNLOAD; + private final String KEY_VIDEO_AUTO_DOWNLOAD; + private final String KEY_ANIMATION_AUTO_DOWNLOAD; + private final String KEY_AUDIO_AUTO_DOWNLOAD; + private final String KEY_NOTIFICATION_PEER_SOUND; private ActorRef settingsSync; @@ -107,6 +113,12 @@ public SettingsModule(ModuleContext context) { KEY_ANIMATION_AUTO_PLAY = "category." + deviceType + ".auto_play.enabled"; + KEY_ANIMATION_AUTO_DOWNLOAD = "category." + deviceType + ".auto_download_animation.enabled"; + KEY_VIDEO_AUTO_DOWNLOAD = "category." + deviceType + ".auto_download_video.enabled"; + KEY_IMAGE_AUTO_DOWNLOAD = "category." + deviceType + ".auto_download_image.enabled"; + KEY_AUDIO_AUTO_DOWNLOAD = "category." + deviceType + ".auto_download_audio.enabled"; + KEY_DOC_AUTO_DOWNLOAD = "category." + deviceType + ".auto_download_doc.enabled"; + // Account-wide notification settings KEY_NOTIFICATION_SOUND = "account.notification.sound"; KEY_NOTIFICATION_GROUP_ENABLED = "account.notifications.group.enabled"; @@ -255,6 +267,47 @@ public void changeTextSize(int textSize) { setInt(KEY_CHAT_TEXT_SIZE, textSize); } + // Auto download settings + + public boolean isImageAutoDownloadEnabled() { + return getBooleanValue(KEY_IMAGE_AUTO_DOWNLOAD, true); + } + + public void setImageAutoDownloadEnabled(boolean enabled) { + setBooleanValue(KEY_IMAGE_AUTO_DOWNLOAD, enabled); + } + + public boolean isAnimationAutoDownloadEnabled() { + return getBooleanValue(KEY_ANIMATION_AUTO_DOWNLOAD, true); + } + + public void setAnimationAutoDownloadEnabled(boolean enabled) { + setBooleanValue(KEY_ANIMATION_AUTO_DOWNLOAD, enabled); + } + + public boolean isVideoAutoDownloadEnabled() { + return getBooleanValue(KEY_VIDEO_AUTO_DOWNLOAD, false); + } + + public void setVideoAutoDownloadEnabled(boolean enabled) { + setBooleanValue(KEY_VIDEO_AUTO_DOWNLOAD, enabled); + } + + public boolean isDocAutoDownloadEnabled() { + return getBooleanValue(KEY_DOC_AUTO_DOWNLOAD, true); + } + + public void setDocAutoDownloadEnabled(boolean enabled) { + setBooleanValue(KEY_DOC_AUTO_DOWNLOAD, enabled); + } + + public boolean isAudioAutoDownloadEnabled() { + return getBooleanValue(KEY_AUDIO_AUTO_DOWNLOAD, true); + } + + public void setAudioAutoDownloadEnabled(boolean enabled) { + setBooleanValue(KEY_AUDIO_AUTO_DOWNLOAD, enabled); + } // Peer settings From 5ba1c308e22e4b3a00bdadaca39d07e233debdaf Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Fri, 23 Sep 2016 01:59:35 +0300 Subject: [PATCH 361/414] wip(iOS): Migrating to Swift 3.0 (not yet working at all - only compiles successfully) --- .../ActorApp.xcodeproj/project.pbxproj | 3 + .../sdk-core-ios/ActorApp/AppDelegate.swift | 16 +- .../ActorSDK.xcodeproj/project.pbxproj | 3 + .../Sources/ActorApplicationDelegate.swift | 32 +- .../Sources/ActorCore/ActorCoreExt.swift | 232 +++--- .../Sources/ActorCore/ActorRuntime.swift | 8 +- .../Providers/CocoaAssetsRuntime.swift | 24 +- .../ActorCore/Providers/CocoaCrypto.swift | 48 +- .../ActorCore/Providers/CocoaDispatcher.swift | 6 +- .../Providers/CocoaFileSystemRuntime.swift | 93 +-- .../Providers/CocoaHttpRuntime.swift | 38 +- .../Providers/CocoaLifecycleRuntime.swift | 10 +- .../Providers/CocoaNetworkRuntime.swift | 52 +- .../Providers/CocoaStorageRuntime.swift | 16 +- .../Providers/CocoaWebRTCRuntime.swift | 148 ++-- .../Providers/Storage/FMDBBridge.swift | 19 +- .../Providers/Storage/FMDBExtensions.swift | 44 +- .../Providers/Storage/FMDBKeyValue.swift | 34 +- .../Providers/Storage/FMDBList.swift | 110 +-- .../Storage/UDPreferencesStorage.swift | 60 +- .../Providers/iOSCallsProvider.swift | 16 +- .../Providers/iOSNotificationProvider.swift | 30 +- .../Providers/iOSPhoneBookProvider.swift | 204 ++--- .../ActorSDK/Sources/ActorSDK.swift | 377 +++++---- .../ActorSDK/Sources/ActorSDKAnalytics.swift | 8 +- .../ActorSDK/Sources/ActorSDKDelegate.swift | 60 +- .../ActorSDK/Sources/ActorStyle.swift | 488 +++++------ .../Auth/AAAuthEmailViewController.swift | 82 +- .../Auth/AAAuthLogInViewController.swift | 62 +- .../Auth/AAAuthNameViewController.swift | 44 +- .../Auth/AAAuthNavigationController.swift | 20 +- .../Auth/AAAuthOTPViewController.swift | 100 +-- .../Auth/AAAuthPhoneViewController.swift | 100 +-- .../Auth/AAAuthViewController.swift | 66 +- .../Auth/AAAuthWelcomeController.swift | 72 +- .../Auth/AABigAlertController.swift | 68 +- .../Auth/AACountryViewController.swift | 72 +- .../Auth/Cells/AAAuthCountryCell.swift | 34 +- .../Calls/AACallViewController.swift | 224 ++--- .../Compose/AAComposeController.swift | 10 +- .../Compose/AAGroupCreateViewController.swift | 90 +- .../Compose/AAGroupMembersController.swift | 60 +- .../Contacts/AAContactsViewController.swift | 104 ++- .../AAContactsListContentController.swift | 18 +- ...ontactsListContentControllerDelegate.swift | 16 +- .../Cells/AAContactActionCell.swift | 20 +- .../Contacts List/Cells/AAContactCell.swift | 38 +- .../Content/Conversation/AABubbles.swift | 6 +- .../AAConversationContentController.swift | 190 ++--- .../Conversation/AANavigationBadge.swift | 32 +- .../Cell/AABubbleBaseFileCell.swift | 42 +- .../Conversation/Cell/AABubbleCell.swift | 238 +++--- .../Cell/AABubbleContactCell.swift | 108 +-- .../Cell/AABubbleDocumentCell.swift | 144 ++-- .../Cell/AABubbleLocationCell.swift | 64 +- .../Conversation/Cell/AABubbleMediaCell.swift | 120 +-- .../Cell/AABubbleServiceCell.swift | 40 +- .../Cell/AABubbleStickerCell.swift | 56 +- .../Conversation/Cell/AABubbleTextCell.swift | 146 ++-- .../Conversation/Cell/AABubbleVoiceCell.swift | 142 ++-- .../AABubbleBackgroundProcessor.swift | 26 +- .../Layouting/AAMessagesFlowLayout.swift | 66 +- .../Layouting/BubbleLayouts.swift | 14 +- .../Conversation/Layouting/Caches.swift | 18 +- .../Suggestion/AAAutoCompleteCell.swift | 30 +- .../Views/AAConvActionSheet.swift | 132 +-- .../Views/AARecordAudioController.swift | 150 ++-- .../Views/AAStickersKeyboard.swift | 60 +- .../Views/AAThumbnailCollectionCell.swift | 26 +- .../Conversation/Views/AAThumbnailView.swift | 192 ++--- .../Views/AAVoiceRecorderView.swift | 60 +- .../AADialogsListContentController.swift | 34 +- ...DialogsListContentControllerDelegate.swift | 6 +- .../Dialogs List/Cells/AADialogCell.swift | 120 ++- .../Cells/AADialogListProcessor.swift | 6 +- .../Cells/AADialogSearchCell.swift | 20 +- .../Content/Input/AAEditFieldController.swift | 52 +- .../Content/Input/AAEditTextController.swift | 44 +- .../Previews/AACorePreviewController.swift | 14 +- .../Previews/AAPhotoPreviewController.swift | 30 +- .../AAWallpapperPreviewController.swift | 56 +- .../WebActions/AAWebActionController.swift | 22 +- .../ConversationViewController.swift | 464 +++++------ .../AAAddParticipantViewController.swift | 24 +- .../AAGroupAdministrationViewController.swift | 28 +- .../Group/AAGroupEditInfoViewController.swift | 78 +- .../Group/AAGroupTypeController.swift | 66 +- .../Group/AAGroupViewController.swift | 120 +-- .../Group/AAGroupViewMembersController.swift | 46 +- .../Group/AAInviteLinkViewController.swift | 30 +- .../Group/Cells/AAGroupMemberCell.swift | 36 +- .../Location/AALocationPickerController.swift | 52 +- .../AACollectionViewController.swift | 18 +- .../AAContentTableController.swift | 40 +- .../AAImagePickerController.swift | 6 +- .../Managed Runtime/AAManagedRange.swift | 54 +- .../Managed Runtime/AAManagedSection.swift | 74 +- .../Managed Runtime/AAManagedTable.swift | 228 ++--- .../AAManagedTableController.swift | 60 +- .../AANavigationController.swift | 16 +- .../AATableViewController.swift | 30 +- .../Managed Runtime/AAViewController.swift | 118 +-- .../Controllers/Managed Runtime/Alerts.swift | 74 +- .../Managed Runtime/Executions.swift | 104 +-- .../Managed Runtime/ManagedAlerts.swift | 38 +- .../Managed Runtime/ManagedBindedCells.swift | 130 +-- .../Managed Runtime/ManagedCells.swift | 424 +++++----- .../ManagedTableExtensions.swift | 10 +- .../Managed Runtime/Navigations.swift | 12 +- .../Recent/AARecentViewController.swift | 40 +- .../Ringtones/AARingtonesViewController.swift | 144 ++-- .../Root/AANoSelectionViewController.swift | 4 +- .../Root/AARootSplitViewController.swift | 16 +- .../Root/AARootTabViewController.swift | 94 ++- .../AASettingsLastSeenController.swift | 60 +- .../AASettingsMediaViewController.swift | 18 +- ...ASettingsNotificationsViewController.swift | 146 ++-- .../AASettingsPrivacyViewController.swift | 10 +- .../AASettingsSessionsController.swift | 22 +- .../Settings/AASettingsViewController.swift | 76 +- .../AASettingsWallpapersController.swift | 54 +- .../Settings/AASettingsWallpapper.swift | 22 +- .../Settings/AAWallpapersCell.swift | 32 +- .../Cells/AAWallpapperPreviewCell.swift | 14 +- .../Cells/AAWallpapperSettingsCell.swift | 64 +- .../User/AAUserViewController.swift | 72 +- .../ElegantPresentationController.swift | 44 +- .../Sources/ElegantPresentations.swift | 50 +- .../Sources/SwiftExtensions/AADevice.swift | 12 +- .../Sources/SwiftExtensions/AALocalized.swift | 12 +- .../Sources/SwiftExtensions/AARegex.swift | 8 +- .../SwiftExtensions/AssosiatedObject.swift | 8 +- .../Sources/SwiftExtensions/Collections.swift | 2 +- .../Sources/SwiftExtensions/Colors.swift | 18 +- .../Sources/SwiftExtensions/Dispatch.swift | 22 +- .../Sources/SwiftExtensions/Fonts.swift | 20 +- .../Sources/SwiftExtensions/Images.swift | 194 ++--- .../Sources/SwiftExtensions/Logs.swift | 4 +- .../Sources/SwiftExtensions/Numbers.swift | 12 +- .../Sources/SwiftExtensions/Promises.swift | 24 +- .../Sources/SwiftExtensions/Strings.swift | 94 +-- .../Sources/SwiftExtensions/Views.swift | 164 ++-- .../Sources/Utils/AAAudioManager.swift | 72 +- .../Sources/Utils/AAAudioRouter.swift | 68 +- .../ActorSDK/Sources/Utils/AAFileTypes.swift | 150 ++-- .../ActorSDK/Sources/Utils/AAHashMap.swift | 8 +- .../ActorSDK/Sources/Utils/AASwiftlyLRU.swift | 12 +- .../ActorSDK/Sources/Utils/AATools.swift | 38 +- .../ActorSDK/Sources/Utils/Bundle.swift | 12 +- ...ustomPresentationAnimationController.swift | 26 +- .../AACustomPresentationController.swift | 24 +- .../Utils/Extensions/AttributedLabel.swift | 106 +-- .../Extensions/TapedLabel/AATapLabel.swift | 104 +-- .../TapedLabel/AATapLabelDelegate.swift | 4 +- .../ActorSDK/Sources/Utils/Reachability.swift | 777 +++++++++--------- .../ActorSDK/Sources/Utils/Telephony.swift | 8 +- .../ActorSDK/Sources/Views/AAAvatarView.swift | 84 +- .../Sources/Views/AABigPlaceholderView.swift | 106 +-- .../Sources/Views/AACircleButton.swift | 48 +- .../Sources/Views/AAMapFastView.swift | 16 +- .../Sources/Views/AAMapPinPointView.swift | 42 +- .../Sources/Views/AAProgressView.swift | 44 +- .../Sources/Views/AAStickerView.swift | 30 +- .../Sources/Views/AATableViewHeader.swift | 10 +- .../Sources/Views/AATableViewSeparator.swift | 4 +- .../Sources/Views/Cells/AAAvatarCell.swift | 32 +- .../Cells/AABackgroundCellRenderer.swift | 26 +- .../Sources/Views/Cells/AACommonCell.swift | 150 ++-- .../Sources/Views/Cells/AAEditCell.swift | 28 +- .../Sources/Views/Cells/AAHeaderCell.swift | 20 +- .../Sources/Views/Cells/AATableViewCell.swift | 34 +- .../Sources/Views/Cells/AATextCell.swift | 24 +- .../Sources/Views/Cells/AATitledCell.swift | 14 +- .../Sources/Views/ViewExtensions.swift | 6 +- .../ActorSDK/Sources/WebRTCExt.swift | 50 +- .../java/im/actor/runtime/power/WakeLock.java | 3 +- 176 files changed, 6085 insertions(+), 6036 deletions(-) diff --git a/actor-sdk/sdk-core-ios/ActorApp.xcodeproj/project.pbxproj b/actor-sdk/sdk-core-ios/ActorApp.xcodeproj/project.pbxproj index eea2566693..3a06e8a7fb 100644 --- a/actor-sdk/sdk-core-ios/ActorApp.xcodeproj/project.pbxproj +++ b/actor-sdk/sdk-core-ios/ActorApp.xcodeproj/project.pbxproj @@ -207,6 +207,7 @@ EA6B7348F8364542DDD264F741AAAA19 = { CreatedOnToolsVersion = 6.4; DevelopmentTeam = HVJR44Y5B6; + LastSwiftMigration = 0800; SystemCapabilities = { com.apple.Maps.iOS = { enabled = 1; @@ -422,6 +423,7 @@ SDK_PATH = "../actor-sdk"; SWIFT_OBJC_BRIDGING_HEADER = ""; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 3.0; USER_HEADER_SEARCH_PATHS = ""; }; name = Debug; @@ -498,6 +500,7 @@ SDK_PATH = "../actor-sdk"; SWIFT_OBJC_BRIDGING_HEADER = ""; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 3.0; USER_HEADER_SEARCH_PATHS = ""; }; name = Release; diff --git a/actor-sdk/sdk-core-ios/ActorApp/AppDelegate.swift b/actor-sdk/sdk-core-ios/ActorApp/AppDelegate.swift index 70eee0d887..5f6c72d8ad 100644 --- a/actor-sdk/sdk-core-ios/ActorApp/AppDelegate.swift +++ b/actor-sdk/sdk-core-ios/ActorApp/AppDelegate.swift @@ -6,7 +6,7 @@ import Foundation import ActorSDK -@objc public class AppDelegate : ActorApplicationDelegate { +open class AppDelegate : ActorApplicationDelegate { override init() { super.init() @@ -14,7 +14,7 @@ import ActorSDK ActorSDK.sharedActor().inviteUrlHost = "quit.email" ActorSDK.sharedActor().inviteUrlScheme = "actor" - ActorSDK.sharedActor().style.searchStatusBarStyle = .Default + ActorSDK.sharedActor().style.searchStatusBarStyle = .default // Enabling experimental features ActorSDK.sharedActor().enableExperimentalFeatures = true @@ -26,7 +26,7 @@ import ActorSDK // Setting Development Push Id ActorSDK.sharedActor().apiPushId = 868547 - ActorSDK.sharedActor().authStrategy = .PhoneEmail + ActorSDK.sharedActor().authStrategy = .phoneEmail ActorSDK.sharedActor().style.dialogAvatarSize = 58 @@ -37,19 +37,19 @@ import ActorSDK } - public override func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject : AnyObject]?) -> Bool { + open override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]?) -> Bool { super.application(application, didFinishLaunchingWithOptions: launchOptions) ActorSDK.sharedActor().presentMessengerInNewWindow() - return true; + return true } - public override func actorRootControllers() -> [UIViewController]? { + open override func actorRootControllers() -> [UIViewController]? { return [AAContactsViewController(), AARecentViewController(), AASettingsViewController()] } - public override func actorRootInitialControllerIndex() -> Int? { + open override func actorRootInitialControllerIndex() -> Int? { return 0 } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK.xcodeproj/project.pbxproj b/actor-sdk/sdk-core-ios/ActorSDK.xcodeproj/project.pbxproj index f096cb3892..7eb0fa4600 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK.xcodeproj/project.pbxproj +++ b/actor-sdk/sdk-core-ios/ActorSDK.xcodeproj/project.pbxproj @@ -1936,6 +1936,7 @@ 066A50D11BC4AE63000E606E = { CreatedOnToolsVersion = 7.0; DevelopmentTeam = HVJR44Y5B6; + LastSwiftMigration = 0800; }; }; }; @@ -2462,6 +2463,7 @@ SWIFT_INCLUDE_PATHS = "$(PROJECT_DIR)/ActorSDK/Sources/Libs/CommonCrypto $(PROJECT_DIR)/IDZSwiftCommonCrypto/Frameworks/$(PLATFORM_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = ""; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 3.0; VALID_ARCHS = "arm64 armv7"; WARNING_CFLAGS = "-Wno-nullability-completeness"; }; @@ -2517,6 +2519,7 @@ SWIFT_INCLUDE_PATHS = "$(PROJECT_DIR)/ActorSDK/Sources/Libs/CommonCrypto $(PROJECT_DIR)/IDZSwiftCommonCrypto/Frameworks/$(PLATFORM_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = ""; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 3.0; VALID_ARCHS = "arm64 armv7"; WARNING_CFLAGS = "-Wno-nullability-completeness"; }; diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorApplicationDelegate.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorApplicationDelegate.swift index 9f717d9083..491e627e6c 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorApplicationDelegate.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorApplicationDelegate.swift @@ -4,7 +4,7 @@ import Foundation -public class ActorApplicationDelegate: ActorSDKDelegateDefault, UIApplicationDelegate { +open class ActorApplicationDelegate: ActorSDKDelegateDefault, UIApplicationDelegate { public override init() { super.init() @@ -12,57 +12,57 @@ public class ActorApplicationDelegate: ActorSDKDelegateDefault, UIApplicationDel ActorSDK.sharedActor().delegate = self } - public func applicationDidFinishLaunching(application: UIApplication) { + open func applicationDidFinishLaunching(_ application: UIApplication) { ActorSDK.sharedActor().applicationDidFinishLaunching(application) } - public func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject : AnyObject]?) -> Bool { + open func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { ActorSDK.sharedActor().applicationDidFinishLaunching(application) return true } - public func applicationDidBecomeActive(application: UIApplication) { + open func applicationDidBecomeActive(_ application: UIApplication) { ActorSDK.sharedActor().applicationDidBecomeActive(application) } - public func applicationWillEnterForeground(application: UIApplication) { + open func applicationWillEnterForeground(_ application: UIApplication) { ActorSDK.sharedActor().applicationWillEnterForeground(application) } - public func applicationDidEnterBackground(application: UIApplication) { + open func applicationDidEnterBackground(_ application: UIApplication) { ActorSDK.sharedActor().applicationDidEnterBackground(application) } - public func applicationWillResignActive(application: UIApplication) { + open func applicationWillResignActive(_ application: UIApplication) { ActorSDK.sharedActor().applicationWillResignActive(application) } - public func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) { + open func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any]) { ActorSDK.sharedActor().application(application, didReceiveRemoteNotification: userInfo) } - public func application(application: UIApplication, didRegisterUserNotificationSettings notificationSettings: UIUserNotificationSettings) { + open func application(_ application: UIApplication, didRegister notificationSettings: UIUserNotificationSettings) { ActorSDK.sharedActor().application(application, didRegisterUserNotificationSettings: notificationSettings) } - public func application(application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: NSData) { + open func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { let tokenString = "\(deviceToken)".replace(" ", dest: "").replace("<", dest: "").replace(">", dest: "") ActorSDK.sharedActor().pushRegisterToken(tokenString) } - public func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) { + open func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { ActorSDK.sharedActor().application(application, didReceiveRemoteNotification: userInfo, fetchCompletionHandler: completionHandler) } - public func application(application: UIApplication, performFetchWithCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) { + open func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { ActorSDK.sharedActor().application(application, performFetchWithCompletionHandler: completionHandler) } - public func application(application: UIApplication, openURL url: NSURL, sourceApplication: String?, annotation: AnyObject) -> Bool { - return ActorSDK.sharedActor().application(application, openURL: url, sourceApplication: sourceApplication, annotation: annotation) + open func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool { + return ActorSDK.sharedActor().application(application, openURL: url, sourceApplication: sourceApplication, annotation: annotation as AnyObject) } - public func application(application: UIApplication, handleOpenURL url: NSURL) -> Bool { + open func application(_ application: UIApplication, handleOpen url: URL) -> Bool { return ActorSDK.sharedActor().application(application, handleOpenURL: url) } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/ActorCoreExt.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/ActorCoreExt.swift index fe8ee59a22..8e52a80ac0 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/ActorCoreExt.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/ActorCoreExt.swift @@ -13,41 +13,41 @@ public var Actor : ACCocoaMessenger { public extension ACCocoaMessenger { - public func sendUIImage(image: NSData, peer: ACPeer, animated:Bool) { + public func sendUIImage(_ image: Data, peer: ACPeer, animated:Bool) { let imageFromData = UIImage(data:image) let thumb = imageFromData!.resizeSquare(90, maxH: 90); let resized = imageFromData!.resizeOptimize(1200 * 1200); let thumbData = UIImageJPEGRepresentation(thumb, 0.55); - let fastThumb = ACFastThumb(int: jint(thumb.size.width), withInt: jint(thumb.size.height), withByteArray: thumbData!.toJavaBytes()) + let fastThumb = ACFastThumb(int: jint(thumb.size.width), with: jint(thumb.size.height), with: thumbData!.toJavaBytes()) - let descriptor = "/tmp/"+NSUUID().UUIDString + let descriptor = "/tmp/"+UUID().uuidString let path = CocoaFiles.pathFromDescriptor(descriptor); - animated ? image.writeToFile(path, atomically: true) : UIImageJPEGRepresentation(resized, 0.80)!.writeToFile(path, atomically: true) + animated ? ((try? image.write(to: URL(fileURLWithPath: path), options: [.atomic])) != nil) : ((try? UIImageJPEGRepresentation(resized, 0.80)!.write(to: URL(fileURLWithPath: path), options: [.atomic])) != nil) - animated ? sendAnimationWithPeer(peer, withName: "image.gif", withW: jint(resized.size.width), withH:jint(resized.size.height), withThumb: fastThumb, withDescriptor: descriptor) : sendPhotoWithPeer(peer, withName: "image.jpg", withW: jint(resized.size.width), withH: jint(resized.size.height), withThumb: fastThumb, withDescriptor: descriptor) + animated ? sendAnimation(with: peer, withName: "image.gif", withW: jint(resized.size.width), withH:jint(resized.size.height), with: fastThumb, withDescriptor: descriptor) : sendPhoto(with: peer, withName: "image.jpg", withW: jint(resized.size.width), withH: jint(resized.size.height), with: fastThumb, withDescriptor: descriptor) } - public func sendVideo(url: NSURL, peer: ACPeer) { + public func sendVideo(_ url: URL, peer: ACPeer) { - if let videoData = NSData(contentsOfURL: url) { // if data have on this local path url go to upload + if let videoData = try? Data(contentsOf: url) { // if data have on this local path url go to upload - let descriptor = "/tmp/"+NSUUID().UUIDString + let descriptor = "/tmp/"+UUID().uuidString let path = CocoaFiles.pathFromDescriptor(descriptor); - videoData.writeToFile(path, atomically: true) // write to file + try? videoData.write(to: URL(fileURLWithPath: path), options: [.atomic]) // write to file // get video duration - let assetforduration = AVURLAsset(URL: url) + let assetforduration = AVURLAsset(url: url) let videoDuration = assetforduration.duration let videoDurationSeconds = CMTimeGetSeconds(videoDuration) // get thubnail and upload - let movieAsset = AVAsset(URL: url) // video asset + let movieAsset = AVAsset(url: url) // video asset let imageGenerator = AVAssetImageGenerator(asset: movieAsset) var thumbnailTime = movieAsset.duration thumbnailTime.value = 25 @@ -55,8 +55,8 @@ public extension ACCocoaMessenger { let orientation = movieAsset.videoOrientation() do { - let imageRef = try imageGenerator.copyCGImageAtTime(thumbnailTime, actualTime: nil) - let thumbnail = UIImage(CGImage: imageRef) + let imageRef = try imageGenerator.copyCGImage(at: thumbnailTime, actualTime: nil) + let thumbnail = UIImage(cgImage: imageRef) var thumb = thumbnail.resizeSquare(90, maxH: 90); let resized = thumbnail.resizeOptimize(1200 * 1200); @@ -65,7 +65,7 @@ public extension ACCocoaMessenger { } let thumbData = UIImageJPEGRepresentation(thumb, 0.55); // thumbnail binary data - let fastThumb = ACFastThumb(int: jint(resized.size.width), withInt: jint(resized.size.height), withByteArray: thumbData!.toJavaBytes()) + let fastThumb = ACFastThumb(int: jint(resized.size.width), with: jint(resized.size.height), with: thumbData!.toJavaBytes()) print("video upload imageRef = \(imageRef)") print("video upload thumbnail = \(thumbnail)") @@ -76,9 +76,9 @@ public extension ACCocoaMessenger { print("video upload height = \(thumbnail.size.height)") if (orientation.orientation.isPortrait == true) { - sendVideoWithPeer(peer, withName: "video.mp4", withW: jint(thumbnail.size.height/2), withH: jint(thumbnail.size.width/2), withDuration: jint(videoDurationSeconds), withThumb: fastThumb, withDescriptor: descriptor) + self.sendVideo(with: peer, withName: "video.mp4", withW: jint(thumbnail.size.height/2), withH: jint(thumbnail.size.width/2), withDuration: jint(videoDurationSeconds), with: fastThumb, withDescriptor: descriptor) } else { - sendVideoWithPeer(peer, withName: "video.mp4", withW: jint(thumbnail.size.width), withH: jint(thumbnail.size.height), withDuration: jint(videoDurationSeconds), withThumb: fastThumb, withDescriptor: descriptor) + self.sendVideo(with: peer, withName: "video.mp4", withW: jint(thumbnail.size.width), withH: jint(thumbnail.size.height), withDuration: jint(videoDurationSeconds), with: fastThumb, withDescriptor: descriptor) } @@ -91,30 +91,30 @@ public extension ACCocoaMessenger { } - private func prepareAvatar(image: UIImage) -> String { - let res = "/tmp/" + NSUUID().UUIDString + fileprivate func prepareAvatar(_ image: UIImage) -> String { + let res = "/tmp/" + UUID().uuidString let avatarPath = CocoaFiles.pathFromDescriptor(res) let thumb = image.resizeSquare(800, maxH: 800); - UIImageJPEGRepresentation(thumb, 0.8)!.writeToFile(avatarPath, atomically: true) + try? UIImageJPEGRepresentation(thumb, 0.8)!.write(to: URL(fileURLWithPath: avatarPath), options: [.atomic]) return res } - public func changeOwnAvatar(image: UIImage) { - changeMyAvatarWithDescriptor(prepareAvatar(image)) + public func changeOwnAvatar(_ image: UIImage) { + changeMyAvatar(withDescriptor: prepareAvatar(image)) } - public func changeGroupAvatar(gid: jint, image: UIImage) -> String { + public func changeGroupAvatar(_ gid: jint, image: UIImage) -> String { let fileName = prepareAvatar(image) - changeGroupAvatarWithGid(gid, withDescriptor: fileName) + self.changeGroupAvatar(withGid: gid, withDescriptor: fileName) return fileName } - public func requestFileState(fileId: jlong, notDownloaded: (()->())?, onDownloading: ((progress: Double) -> ())?, onDownloaded: ((reference: String) -> ())?) { - Actor.requestStateWithFileId(fileId, withCallback: AAFileCallback(notDownloaded: notDownloaded, onDownloading: onDownloading, onDownloaded: onDownloaded)) + public func requestFileState(_ fileId: jlong, notDownloaded: (()->())?, onDownloading: ((_ progress: Double) -> ())?, onDownloaded: ((_ reference: String) -> ())?) { + Actor.requestState(withFileId: fileId, with: AAFileCallback(notDownloaded: notDownloaded, onDownloading: onDownloading, onDownloaded: onDownloaded)) } - public func requestFileState(fileId: jlong, onDownloaded: ((reference: String) -> ())?) { - Actor.requestStateWithFileId(fileId, withCallback: AAFileCallback(notDownloaded: nil, onDownloading: nil, onDownloaded: onDownloaded)) + public func requestFileState(_ fileId: jlong, onDownloaded: ((_ reference: String) -> ())?) { + Actor.requestState(withFileId: fileId, with: AAFileCallback(notDownloaded: nil, onDownloading: nil, onDownloaded: onDownloaded)) } } @@ -122,10 +122,10 @@ public extension ACCocoaMessenger { // Collcections // -extension JavaUtilAbstractCollection : SequenceType { +extension JavaUtilAbstractCollection : Sequence { - public func generate() -> NSFastGenerator { - return NSFastGenerator(self) + public func makeIterator() -> NSFastEnumerationIterator { + return NSFastEnumerationIterator(self) } } @@ -134,7 +134,7 @@ public extension JavaUtilList { public func toSwiftArray() -> [T] { var res = [T]() for i in 0..() -> [T] { var res = [T]() for i in 0.. IOSByteArray { - return IOSByteArray(bytes: UnsafePointer(self.bytes), count: UInt(self.length)) + return IOSByteArray(bytes: (self as NSData).bytes.bindMemory(to: jbyte.self, capacity: self.count), count: UInt(self.count)) } } @@ -164,13 +164,13 @@ public extension ACPeer { public var isGroup: Bool { get { - return self.peerType.ordinal() == ACPeerType.GROUP().ordinal() + return self.peerType.ordinal() == ACPeerType.group().ordinal() } } public var isPrivate: Bool { get { - return self.peerType.ordinal() == ACPeerType.PRIVATE().ordinal() + return self.peerType.ordinal() == ACPeerType.private().ordinal() } } } @@ -188,35 +188,35 @@ public extension ACMessage { // Callbacks // -public class AACommandCallback: NSObject, ACCommandCallback { +open class AACommandCallback: NSObject, ACCommandCallback { - public var resultClosure: ((val: AnyObject!) -> ())?; - public var errorClosure: ((val:JavaLangException!) -> ())?; + open var resultClosure: ((_ val: Any?) -> ())?; + open var errorClosure: ((_ val:JavaLangException?) -> ())?; - public init(result: ((val:T?) -> ())?, error: ((val:JavaLangException!) -> ())?) { + public init(result: ((_ val:T?) -> ())?, error: ((_ val:JavaLangException?) -> ())?) { super.init() - self.resultClosure = { (val: AnyObject!) -> () in - result?(val: val as? T) + self.resultClosure = { (val: Any!) -> () in + (result?(val as? T))! } self.errorClosure = error } - public func onResult(res: AnyObject!) { - resultClosure?(val: res) + open func onResult(_ res: Any!) { + resultClosure?(res) } - public func onError(e: JavaLangException!) { - errorClosure?(val: e) + open func onError(_ e: JavaLangException!) { + errorClosure?(e) } } class AAUploadFileCallback : NSObject, ACUploadFileCallback { let notUploaded: (()->())? - let onUploading: ((progress: Double) -> ())? + let onUploading: ((_ progress: Double) -> ())? let onUploadedClosure: (() -> ())? - init(notUploaded: (()->())?, onUploading: ((progress: Double) -> ())?, onUploadedClosure: (() -> ())?) { + init(notUploaded: (()->())?, onUploading: ((_ progress: Double) -> ())?, onUploadedClosure: (() -> ())?) { self.onUploading = onUploading self.notUploaded = notUploaded self.onUploadedClosure = onUploadedClosure; @@ -230,24 +230,24 @@ class AAUploadFileCallback : NSObject, ACUploadFileCallback { self.onUploadedClosure?() } - func onUploading(progress: jfloat) { - self.onUploading?(progress: Double(progress)) + func onUploading(_ progress: jfloat) { + self.onUploading?(Double(progress)) } } class AAFileCallback : NSObject, ACFileCallback { let notDownloaded: (()->())? - let onDownloading: ((progress: Double) -> ())? - let onDownloaded: ((fileName: String) -> ())? + let onDownloading: ((_ progress: Double) -> ())? + let onDownloaded: ((_ fileName: String) -> ())? - init(notDownloaded: (()->())?, onDownloading: ((progress: Double) -> ())?, onDownloaded: ((reference: String) -> ())?) { + init(notDownloaded: (()->())?, onDownloading: ((_ progress: Double) -> ())?, onDownloaded: ((_ reference: String) -> ())?) { self.notDownloaded = notDownloaded; self.onDownloading = onDownloading; self.onDownloaded = onDownloaded; } - init(onDownloaded: (reference: String) -> ()) { + init(onDownloaded: @escaping (_ reference: String) -> ()) { self.notDownloaded = nil; self.onDownloading = nil; self.onDownloaded = onDownloaded; @@ -257,12 +257,12 @@ class AAFileCallback : NSObject, ACFileCallback { self.notDownloaded?(); } - func onDownloading(progress: jfloat) { - self.onDownloading?(progress: Double(progress)); + func onDownloading(_ progress: jfloat) { + self.onDownloading?(Double(progress)); } - func onDownloaded(reference: ARFileSystemReference!) { - self.onDownloaded?(fileName: reference!.getDescriptor()); + func onDownloaded(_ reference: ARFileSystemReference!) { + self.onDownloaded?(reference!.getDescriptor()); } } @@ -284,13 +284,13 @@ class AAFileCallback : NSObject, ACFileCallback { // } //} -public class TextParser { +open class TextParser { - public let textColor: UIColor - public let linkColor: UIColor - public let fontSize: CGFloat + open let textColor: UIColor + open let linkColor: UIColor + open let fontSize: CGFloat - private let markdownParser = ARMarkdownParser(int: ARMarkdownParser_MODE_FULL) + fileprivate let markdownParser = ARMarkdownParser(int: ARMarkdownParser_MODE_FULL) public init(textColor: UIColor, linkColor: UIColor, fontSize: CGFloat) { self.textColor = textColor @@ -298,10 +298,10 @@ public class TextParser { self.fontSize = fontSize } - public func parse(text: String) -> ParsedText { - let doc = markdownParser.processDocumentWithNSString(text) + open func parse(_ text: String) -> ParsedText { + let doc = markdownParser?.processDocument(with: text) - if doc.isTrivial() { + if (doc?.isTrivial())! { let nAttrText = NSMutableAttributedString(string: text) let range = NSRange(location: 0, length: nAttrText.length) nAttrText.yy_setColor(textColor, range: range) @@ -311,12 +311,12 @@ public class TextParser { var sources = [String]() - let sections: [ARMDSection] = doc.getSections().toSwiftArray() + let sections: [ARMDSection] = doc!.getSections().toSwiftArray() let nAttrText = NSMutableAttributedString() var isFirst = true for s in sections { if !isFirst { - nAttrText.appendAttributedString(NSAttributedString(string: "\n")) + // nAttrText.append(NSAttributedString(string: "\n")) } isFirst = false @@ -331,13 +331,13 @@ public class TextParser { str.yy_setFont(UIFont.textFontOfSize(fontSize), range: range) str.yy_setColor(linkColor, range: range) - nAttrText.appendAttributedString(str) + // nAttrText.append(str) sources.append(s.getCode().getCode()) } else if s.getType() == ARMDSection_TYPE_TEXT { let child: [ARMDText] = s.getText().toSwiftArray() for c in child { - nAttrText.appendAttributedString(buildText(c, fontSize: fontSize)) + // nAttrText.append(buildText(c, fontSize: fontSize)) } } else { fatalError("Unsupported section type") @@ -347,7 +347,7 @@ public class TextParser { return ParsedText(attributedText: nAttrText, isTrivial: false, code: sources) } - private func buildText(text: ARMDText, fontSize: CGFloat) -> NSAttributedString { + fileprivate func buildText(_ text: ARMDText, fontSize: CGFloat) -> NSAttributedString { if let raw = text as? ARMDRawText { let res = NSMutableAttributedString(string: raw.getRawText()) let range = NSRange(location: 0, length: res.length) @@ -363,14 +363,14 @@ public class TextParser { // Processing child texts let child: [ARMDText] = span.getChild().toSwiftArray() for c in child { - res.appendAttributedString(buildText(c, fontSize: fontSize)) + // res.append(buildText(c, fontSize: fontSize)) } // Setting span elements - if span.getSpanType() == ARMDSpan_TYPE_BOLD { - res.appendFont(UIFont.boldSystemFontOfSize(fontSize)) - } else if span.getSpanType() == ARMDSpan_TYPE_ITALIC { - res.appendFont(UIFont.italicSystemFontOfSize(fontSize)) + if span.getType() == ARMDSpan_TYPE_BOLD { + res.appendFont(UIFont.boldSystemFont(ofSize: fontSize)) + } else if span.getType() == ARMDSpan_TYPE_ITALIC { + res.appendFont(UIFont.italicSystemFont(ofSize: fontSize)) } else { fatalError("Unsupported span type") } @@ -380,9 +380,9 @@ public class TextParser { } else if let url = text as? ARMDUrl { // Parsing url element - let nsUrl = NSURL(string: url.getUrl()) + let nsUrl = URL(string: url.getUrl()) if nsUrl != nil { - let res = NSMutableAttributedString(string: url.getUrlTitle()) + let res = NSMutableAttributedString(string: url.getTitle()) let range = NSRange(location: 0, length: res.length) let highlight = YYTextHighlight() highlight.userInfo = ["url" : url.getUrl()] @@ -392,7 +392,7 @@ public class TextParser { return res } else { // Unable to parse: show as text - let res = NSMutableAttributedString(string: url.getUrlTitle()) + let res = NSMutableAttributedString(string: url.getTitle()) let range = NSRange(location: 0, length: res.length) res.beginEditing() res.yy_setFont(UIFont.textFontOfSize(fontSize), range: range) @@ -406,11 +406,11 @@ public class TextParser { } } -public class ParsedText { +open class ParsedText { - public let isTrivial: Bool - public let attributedText: NSAttributedString - public let code: [String] + open let isTrivial: Bool + open let attributedText: NSAttributedString + open let code: [String] public init(attributedText: NSAttributedString, isTrivial: Bool, code: [String]) { self.attributedText = attributedText @@ -532,20 +532,20 @@ public class ParsedText { // Promises // -public class AAPromiseFunc: NSObject, ARPromiseFunc { +open class AAPromiseFunc: NSObject, ARPromiseFunc { - let closure: (resolver: ARPromiseResolver) -> () - init(closure: (resolver: ARPromiseResolver) -> ()){ + let closure: (_ resolver: ARPromiseResolver) -> () + init(closure: @escaping (_ resolver: ARPromiseResolver) -> ()){ self.closure = closure } - public func exec(resolver: ARPromiseResolver) { - closure(resolver: resolver) + open func exec(_ resolver: ARPromiseResolver) { + closure(resolver) } } extension ARPromise { - convenience init(closure: (resolver: ARPromiseResolver) -> ()) { + convenience init(closure: @escaping (_ resolver: ARPromiseResolver) -> ()) { self.init(executor: AAPromiseFunc(closure: closure)) } } @@ -554,77 +554,77 @@ extension ARPromise { // Data Binding // -public class AABinder { +open class AABinder { - private var bindings : [BindHolder] = [] + fileprivate var bindings : [BindHolder] = [] public init() { } - public func bind(valueModel1:ARValue, valueModel2:ARValue, valueModel3:ARValue, closure: (value1:T1!, value2:T2!, value3:T3!) -> ()) { + open func bind(_ valueModel1:ARValue, valueModel2:ARValue, valueModel3:ARValue, closure: @escaping (_ value1:T1?, _ value2:T2?, _ value3:T3?) -> ()) { let listener1 = BindListener { (_value1) -> () in - closure(value1: _value1 as? T1, value2: valueModel2.get() as? T2, value3: valueModel3.get() as? T3) + closure(_value1 as? T1, valueModel2.get() as? T2, valueModel3.get() as? T3) }; let listener2 = BindListener { (_value2) -> () in - closure(value1: valueModel1.get() as? T1, value2: _value2 as? T2, value3: valueModel3.get() as? T3) + closure(valueModel1.get() as? T1, _value2 as? T2, valueModel3.get() as? T3) }; let listener3 = BindListener { (_value3) -> () in - closure(value1: valueModel1.get() as? T1, value2: valueModel2.get() as? T2, value3: _value3 as? T3) + closure(valueModel1.get() as? T1, valueModel2.get() as? T2, _value3 as? T3) }; bindings.append(BindHolder(valueModel: valueModel1, listener: listener1)) bindings.append(BindHolder(valueModel: valueModel2, listener: listener2)) bindings.append(BindHolder(valueModel: valueModel3, listener: listener3)) - valueModel1.subscribeWithListener(listener1, notify: false) - valueModel2.subscribeWithListener(listener2, notify: false) - valueModel3.subscribeWithListener(listener3, notify: false) - closure(value1: valueModel1.get() as? T1, value2: valueModel2.get() as? T2, value3: valueModel3.get() as? T3) + valueModel1.subscribe(with: listener1, notify: false) + valueModel2.subscribe(with: listener2, notify: false) + valueModel3.subscribe(with: listener3, notify: false) + closure(valueModel1.get() as? T1, valueModel2.get() as? T2, valueModel3.get() as? T3) } - public func bind(valueModel1:ARValue, valueModel2:ARValue, closure: (value1:T1!, value2:T2!) -> ()) { + open func bind(_ valueModel1:ARValue, valueModel2:ARValue, closure: @escaping (_ value1:T1?, _ value2:T2?) -> ()) { let listener1 = BindListener { (_value1) -> () in - closure(value1: _value1 as? T1, value2: valueModel2.get() as? T2) + closure(_value1 as? T1, valueModel2.get() as? T2) }; let listener2 = BindListener { (_value2) -> () in - closure(value1: valueModel1.get() as? T1, value2: _value2 as? T2) + closure(valueModel1.get() as? T1, _value2 as? T2) }; bindings.append(BindHolder(valueModel: valueModel1, listener: listener1)) bindings.append(BindHolder(valueModel: valueModel2, listener: listener2)) - valueModel1.subscribeWithListener(listener1, notify: false) - valueModel2.subscribeWithListener(listener2, notify: false) - closure(value1: valueModel1.get() as? T1, value2: valueModel2.get() as? T2) + valueModel1.subscribe(with: listener1, notify: false) + valueModel2.subscribe(with: listener2, notify: false) + closure(valueModel1.get() as? T1, valueModel2.get() as? T2) } - public func bind(value:ARValue, closure: (value: T!)->()) { + open func bind(_ value:ARValue, closure: @escaping (_ value: T?)->()) { let listener = BindListener { (value2) -> () in - closure(value: value2 as? T) + closure(value2 as? T) }; let holder = BindHolder(valueModel: value, listener: listener) bindings.append(holder) - value.subscribeWithListener(listener) + value.subscribe(with: listener) } - public func unbindAll() { + open func unbindAll() { for holder in bindings { - holder.valueModel.unsubscribeWithListener(holder.listener) + holder.valueModel.unsubscribe(with: holder.listener) } - bindings.removeAll(keepCapacity: true) + bindings.removeAll(keepingCapacity: true) } } class BindListener: NSObject, ARValueChangedListener { - var closure: ((value: AnyObject?)->())? + var closure: ((_ value: Any?)->())? - init(closure: (value: AnyObject?)->()) { + init(closure: @escaping (_ value: Any?)->()) { self.closure = closure } - func onChanged(val: AnyObject!, withModel valueModel: ARValue!) { - closure?(value: val) + func onChanged(_ val: Any!, withModel valueModel: ARValue!) { + closure?(val) } } @@ -636,4 +636,4 @@ class BindHolder { self.valueModel = valueModel self.listener = listener } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/ActorRuntime.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/ActorRuntime.swift index 518f7dc771..e40d5ddd4b 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/ActorRuntime.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/ActorRuntime.swift @@ -6,7 +6,7 @@ import Foundation class AAActorRuntime { - private static var isInited = false + fileprivate static var isInited = false class func configureRuntime() { if isInited { @@ -18,11 +18,11 @@ class AAActorRuntime { ARCocoaHttpProxyProvider.setHttpRuntime(CocoaHttpRuntime()) ARCocoaFileSystemProxyProvider.setFileSystemRuntime(CocoaFileSystemRuntime()) ARCocoaNetworkProxyProvider.setNetworkRuntime(CocoaNetworkRuntime()) - ARCocoaAssetsProxyProvider.setAssetsRuntimeWithARAssetsRuntime(CocoaAssetsRuntime()) + ARCocoaAssetsProxyProvider.setAssetsRuntimeWith(CocoaAssetsRuntime()) ARCocoaWebRTCProxyProvider.setWebRTCRuntime(CocoaWebRTCRuntime()) ARCocoaLifecycleProxyProvider.setLifecycleRuntime(CocoaLifecycleRuntime()) ARCocoaDispatcher.setDispatcherProxy(CocoaDispatcher()) // ARRuntimeEnvironment.setIsProductionWithBoolean(true) - ARCocoaCryptoProvider.setProxyProviderWithARCocoaCryptoProxyProvider(CocoaCrypto()) + ARCocoaCryptoProvider.setProxyProviderWithARCocoaCryptoProxy(CocoaCrypto()) } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/CocoaAssetsRuntime.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/CocoaAssetsRuntime.swift index 0f8fcf6e02..152e2bc8f9 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/CocoaAssetsRuntime.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/CocoaAssetsRuntime.swift @@ -6,19 +6,19 @@ import Foundation class CocoaAssetsRuntime: NSObject, ARAssetsRuntime { - func hasAssetWithNSString(name: String!) -> jboolean { - if NSBundle.mainBundle().pathForResource(name, ofType: nil) != nil { + func hasAsset(with name: String!) -> jboolean { + if Bundle.main.path(forResource: name, ofType: nil) != nil { return true } - if NSBundle.framework.pathForResource(name, ofType: nil) != nil { + if Bundle.framework.path(forResource: name, ofType: nil) != nil { return true } return false } - func loadAssetWithNSString(name: String!) -> String! { + func loadAsset(with name: String!) -> String! { var path: String? - path = NSBundle.mainBundle().pathForResource(name, ofType: nil) + path = Bundle.main.path(forResource: name, ofType: nil) if path != nil { do { return try String(contentsOfFile: path!) @@ -27,7 +27,7 @@ class CocoaAssetsRuntime: NSObject, ARAssetsRuntime { } } - path = NSBundle.framework.pathForResource(name, ofType: nil) + path = Bundle.framework.path(forResource: name, ofType: nil) if path != nil { do { return try String(contentsOfFile: path!) @@ -39,22 +39,22 @@ class CocoaAssetsRuntime: NSObject, ARAssetsRuntime { return nil } - func loadBinAssetWithNSString(name: String!) -> IOSByteArray! { + func loadBinAsset(with name: String!) -> IOSByteArray! { var path: String? - path = NSBundle.mainBundle().pathForResource(name, ofType: nil) + path = Bundle.main.path(forResource: name, ofType: nil) if path != nil { - if let data = NSData(contentsOfFile: path!) { + if let data = try? Data(contentsOf: URL(fileURLWithPath: path!)) { return data.toJavaBytes() } } - path = NSBundle.framework.pathForResource(name, ofType: nil) + path = Bundle.framework.path(forResource: name, ofType: nil) if path != nil { - if let data = NSData(contentsOfFile: path!) { + if let data = try? Data(contentsOf: URL(fileURLWithPath: path!)) { return data.toJavaBytes() } } return nil } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/CocoaCrypto.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/CocoaCrypto.swift index 71c6adf5bf..51468716ae 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/CocoaCrypto.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/CocoaCrypto.swift @@ -11,14 +11,14 @@ class CocoaCrypto: NSObject, ARCocoaCryptoProxyProvider { return SHA256Digest() } - func createAES128WithKey(key: IOSByteArray!) -> ARBlockCipher! { + func createAES128(withKey key: IOSByteArray!) -> ARBlockCipher! { return AES128(key: key) } } class SHA256Digest: NSObject, ARDigest { - let context = UnsafeMutablePointer.alloc(1) + let context = UnsafeMutablePointer.allocate(capacity: 1) override init() { super.init() @@ -26,39 +26,39 @@ class SHA256Digest: NSObject, ARDigest { } deinit { - context.dealloc(1) + context.deallocate(capacity: 1) } func reset() { CC_SHA256_Init(context) } - func update(src: IOSByteArray!, withOffset offset: jint, withLength length: jint) { + func update(_ src: IOSByteArray!, withOffset offset: jint, withLength length: jint) { let pointer = src .buffer() - .advancedBy(Int(offset)) + .advanced(by: Int(offset)) CC_SHA256_Update(context, pointer, CC_LONG(length)) } - func doFinal(dest: IOSByteArray!, withOffset destOffset: jint) { + func doFinal(_ dest: IOSByteArray!, withOffset destOffset: jint) { - let pointer = UnsafeMutablePointer(dest.buffer()) - .advancedBy(Int(destOffset)) - - CC_SHA256_Final(pointer, context) +// let pointer = UnsafeMutablePointer(dest.buffer()) +// .advanced(by: Int(destOffset)) +// +// CC_SHA256_Final(pointer, context) } - func getDigestSize() -> jint { + func getSize() -> jint { return jint(CC_SHA256_DIGEST_LENGTH) } } class AES128: NSObject, ARBlockCipher { - var encryptor = UnsafeMutablePointer.alloc(1) - var decryptor = UnsafeMutablePointer.alloc(1) + var encryptor = UnsafeMutablePointer.allocate(capacity: 1) + var decryptor = UnsafeMutablePointer.allocate(capacity: 1) init(key: IOSByteArray) { CCCryptorCreate( @@ -79,33 +79,33 @@ class AES128: NSObject, ARBlockCipher { } deinit { - encryptor.dealloc(1) - decryptor.dealloc(1) + encryptor.deallocate(capacity: 1) + decryptor.deallocate(capacity: 1) } - func encryptBlock(data: IOSByteArray!, withOffset offset: jint, toDest dest: IOSByteArray!, withOffset destOffset: jint) { + func encryptBlock(_ data: IOSByteArray!, withOffset offset: jint, toDest dest: IOSByteArray!, withOffset destOffset: jint) { let src = data.buffer() - .advancedBy(Int(offset)) + .advanced(by: Int(offset)) let dst = dest.buffer() - .advancedBy(Int(destOffset)) + .advanced(by: Int(destOffset)) var bytesOut: Int = 0 - CCCryptorUpdate(encryptor.memory, src, 16, dst, 32, &bytesOut) + CCCryptorUpdate(encryptor.pointee, src, 16, dst, 32, &bytesOut) } - func decryptBlock(data: IOSByteArray!, withOffset offset: jint, toDest dest: IOSByteArray!, withOffset destOffset: jint) { + func decryptBlock(_ data: IOSByteArray!, withOffset offset: jint, toDest dest: IOSByteArray!, withOffset destOffset: jint) { let src = data.buffer() - .advancedBy(Int(offset)) + .advanced(by: Int(offset)) let dst = dest.buffer() - .advancedBy(Int(destOffset)) + .advanced(by: Int(destOffset)) var bytesOut: Int = 0 - CCCryptorUpdate(decryptor.memory, src, 16, dst, 32, &bytesOut) + CCCryptorUpdate(decryptor.pointee, src, 16, dst, 32, &bytesOut) } func getBlockSize() -> jint { return 16 } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/CocoaDispatcher.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/CocoaDispatcher.swift index dd25624673..3fd384ab11 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/CocoaDispatcher.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/CocoaDispatcher.swift @@ -6,7 +6,7 @@ import Foundation class CocoaDispatcher: NSObject, ARCocoaDispatcherProxy { - func dispatchOnBackground(runnable: JavaLangRunnable!, withDelay delay: jlong) -> ARDispatchCancel! { + func dispatch(onBackground runnable: JavaLangRunnable!, withDelay delay: jlong) -> ARDispatchCancel! { dispatchBackgroundDelayed(Double(delay) / 1000) { () -> Void in runnable.run() } @@ -16,7 +16,7 @@ class CocoaDispatcher: NSObject, ARCocoaDispatcherProxy { private class DispatchCancel: NSObject, ARDispatchCancel { - @objc private func cancel() { + @objc fileprivate func cancel() { // Do Nothing } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/CocoaFileSystemRuntime.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/CocoaFileSystemRuntime.swift index 416b74b402..f74c4eb3f5 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/CocoaFileSystemRuntime.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/CocoaFileSystemRuntime.swift @@ -4,11 +4,11 @@ import Foundation -private let documentsFolder = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true)[0].asNS.stringByDeletingLastPathComponent +private let documentsFolder = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true)[0].asNS.deletingLastPathComponent // Public methods for working with files -public class CocoaFiles { - public class func pathFromDescriptor(path: String) -> String { +open class CocoaFiles { + open class func pathFromDescriptor(_ path: String) -> String { return documentsFolder + path } } @@ -17,19 +17,19 @@ public class CocoaFiles { @objc class CocoaFileSystemRuntime : NSObject, ARFileSystemRuntime { - let manager = NSFileManager.defaultManager() + let manager = FileManager.default override init() { super.init() } func createTempFile() -> ARFileSystemReference! { - let fileName = "/tmp/\(NSUUID().UUIDString)" - NSFileManager.defaultManager().createFileAtPath(documentsFolder + fileName, contents: nil, attributes: nil) + let fileName = "/tmp/\(UUID().uuidString)" + FileManager.default.createFile(atPath: documentsFolder + fileName, contents: nil, attributes: nil) return CocoaFile(path: fileName) } - func commitTempFile(sourceFile: ARFileSystemReference!, withFileId fileId: jlong, withFileName fileName: String!) -> ARFileSystemReference! { + func commitTempFile(_ sourceFile: ARFileSystemReference!, withFileId fileId: jlong, withFileName fileName: String!) -> ARFileSystemReference! { // Finding file available name @@ -44,15 +44,15 @@ public class CocoaFiles { // } // } - let srcUrl = NSURL(fileURLWithPath: documentsFolder + sourceFile.getDescriptor()!) - let destUrl = NSURL(fileURLWithPath: documentsFolder + descriptor) + let srcUrl = URL(fileURLWithPath: documentsFolder + sourceFile.getDescriptor()!) + let destUrl = URL(fileURLWithPath: documentsFolder + descriptor) // manager.replaceItemAtURL(srcUrl, withItemAtURL: destUrl, backupItemName: nil, options: 0, resultingItemURL: nil) // Moving file to new place do { - try manager.replaceItemAtURL(destUrl, withItemAtURL: srcUrl, backupItemName: nil, options: NSFileManagerItemReplacementOptions(rawValue: 0), resultingItemURL: nil) + try manager.replaceItem(at: destUrl, withItemAt: srcUrl, backupItemName: nil, options: FileManager.ItemReplacementOptions(rawValue: 0), resultingItemURL: nil) // try manager.moveItemAtPath(documentsFolder + sourceFile.getDescriptor()!, toPath: path) return CocoaFile(path: descriptor) @@ -61,7 +61,7 @@ public class CocoaFiles { } } - func fileFromDescriptor(descriptor: String!) -> ARFileSystemReference! { + func file(fromDescriptor descriptor: String!) -> ARFileSystemReference! { return CocoaFile(path: descriptor) } @@ -85,7 +85,7 @@ class CocoaFile : NSObject, ARFileSystemReference { } func isExist() -> Bool { - return NSFileManager().fileExistsAtPath(realPath); + return FileManager().fileExists(atPath: realPath); } func isInAppMemory() -> jboolean { @@ -98,8 +98,9 @@ class CocoaFile : NSObject, ARFileSystemReference { func getSize() -> jint { do { - let attrs = try NSFileManager().attributesOfItemAtPath(realPath) - return jint(NSDictionary.fileSize(attrs)()) + let attrs = try FileManager().attributesOfItem(atPath: realPath) + // return jint(Dictionary.fileSize(attrs)()) + return 0 } catch _ { return 0 } @@ -107,24 +108,24 @@ class CocoaFile : NSObject, ARFileSystemReference { func openRead() -> ARPromise! { - let fileHandle = NSFileHandle(forReadingAtPath: realPath) + let fileHandle = FileHandle(forReadingAtPath: realPath) if (fileHandle == nil) { - return ARPromise.failure(JavaLangRuntimeException(NSString: "Unable to open file")) + return ARPromise.failure(JavaLangRuntimeException(nsString: "Unable to open file")) } return ARPromise.success(CocoaInputFile(fileHandle: fileHandle!)) } - func openWriteWithSize(size: jint) -> ARPromise! { - let fileHandle = NSFileHandle(forWritingAtPath: realPath) + func openWrite(withSize size: jint) -> ARPromise! { + let fileHandle = FileHandle(forWritingAtPath: realPath) if (fileHandle == nil) { - return ARPromise.failure(JavaLangRuntimeException(NSString: "Unable to open file")) + return ARPromise.failure(JavaLangRuntimeException(nsString: "Unable to open file")) } - fileHandle!.seekToFileOffset(UInt64(size)) - fileHandle!.seekToFileOffset(0) + fileHandle!.seek(toFileOffset: UInt64(size)) + fileHandle!.seek(toFileOffset: 0) return ARPromise.success(CocoaOutputFile(fileHandle: fileHandle!)) } @@ -132,19 +133,19 @@ class CocoaFile : NSObject, ARFileSystemReference { class CocoaOutputFile : NSObject, AROutputFile { - let fileHandle: NSFileHandle + let fileHandle: FileHandle - init(fileHandle:NSFileHandle) { + init(fileHandle:FileHandle) { self.fileHandle = fileHandle } - func writeWithOffset(fileOffset: jint, withData data: IOSByteArray!, withDataOffset dataOffset: jint, withLength dataLen: jint) -> Bool { + func write(withOffset fileOffset: jint, withData data: IOSByteArray!, withDataOffset dataOffset: jint, withLength dataLen: jint) -> Bool { - let pointer = data.buffer().advancedBy(Int(dataOffset)) - let srcData = NSData(bytesNoCopy: pointer, length: Int(dataLen), freeWhenDone: false) + let pointer = data.buffer().advanced(by: Int(dataOffset)) + let srcData = Data(bytesNoCopy: UnsafeMutableRawPointer(pointer), count: Int(dataLen), deallocator: .none) - fileHandle.seekToFileOffset(UInt64(fileOffset)) - fileHandle.writeData(srcData) + fileHandle.seek(toFileOffset: UInt64(fileOffset)) + fileHandle.write(srcData) return true; } @@ -158,30 +159,30 @@ class CocoaOutputFile : NSObject, AROutputFile { class CocoaInputFile :NSObject, ARInputFile { - let fileHandle:NSFileHandle + let fileHandle:FileHandle - init(fileHandle:NSFileHandle) { + init(fileHandle:FileHandle) { self.fileHandle = fileHandle } - func readWithOffset(fileOffset: jint, withLength len: jint) -> ARPromise! { + func read(withOffset fileOffset: jint, withLength len: jint) -> ARPromise! { return ARPromise { (resolver) in dispatchBackground { - self.fileHandle.seekToFileOffset(UInt64(fileOffset)) - - let readed: NSData = self.fileHandle.readDataOfLength(Int(len)) - let data = IOSByteArray(length: UInt(len)) - var srcBuffer = UnsafeMutablePointer(readed.bytes) - var destBuffer = UnsafeMutablePointer(data.buffer()) - let readCount = min(Int(len), Int(readed.length)) - for _ in 0..(mutating: (readed as NSData).bytes.bindMemory(to: UInt8.self, capacity: readed.count)) +// var destBuffer = UnsafeMutableRawPointer(data!.buffer()) +// let readCount = min(Int(len), Int(readed.count)) +// for _ in 0.. ARPromise! { + func getMethodWithUrl(_ url: String!, withStartOffset startOffset: jint, withSize size: jint, withTotalSize totalSize: jint) -> ARPromise! { return ARPromise { (resolver) in let header = "bytes=\(startOffset)-\(min(startOffset + size, totalSize))" - let request = NSMutableURLRequest(URL: NSURL(string: url)!) - request.HTTPShouldHandleCookies = false - request.cachePolicy = NSURLRequestCachePolicy.ReloadIgnoringLocalAndRemoteCacheData + let request = NSMutableURLRequest(url: URL(string: url)!) + request.httpShouldHandleCookies = false + request.cachePolicy = NSURLRequest.CachePolicy.reloadIgnoringLocalAndRemoteCacheData request.setValue(header, forHTTPHeaderField: "Range") - request.HTTPMethod = "GET" + request.httpMethod = "GET" - NSURLConnection.sendAsynchronousRequest(request, queue: self.queue, completionHandler:{ (response: NSURLResponse?, data: NSData?, error: NSError?) -> Void in - if let respHttp = response as? NSHTTPURLResponse { + NSURLConnection.sendAsynchronousRequest(request as URLRequest, queue: self.queue, completionHandler:{ (response: URLResponse?, data: Data?, error: NSError?) -> Void in + if let respHttp = response as? HTTPURLResponse { if (respHttp.statusCode >= 200 && respHttp.statusCode < 300) { resolver.result(ARHTTPResponse(code: jint(respHttp.statusCode), withContent: data!.toJavaBytes())) } else { @@ -29,21 +29,21 @@ class CocoaHttpRuntime: NSObject, ARHttpRuntime { } else { resolver.error(ARHTTPError(int: 0)) } - }) + } as! (URLResponse?, Data?, Error?) -> Void) } } - func putMethodWithUrl(url: String!, withContents contents: IOSByteArray!) -> ARPromise! { + func putMethod(withUrl url: String!, withContents contents: IOSByteArray!) -> ARPromise! { return ARPromise { (resolver) in - let request = NSMutableURLRequest(URL: NSURL(string: url)!) - request.HTTPShouldHandleCookies = false - request.cachePolicy = NSURLRequestCachePolicy.ReloadIgnoringLocalAndRemoteCacheData - request.HTTPMethod = "PUT" - request.HTTPBody = contents.toNSData() + let request = NSMutableURLRequest(url: URL(string: url)!) + request.httpShouldHandleCookies = false + request.cachePolicy = NSURLRequest.CachePolicy.reloadIgnoringLocalAndRemoteCacheData + request.httpMethod = "PUT" + request.httpBody = contents.toNSData() request.setValue("application/octet-stream", forHTTPHeaderField: "Content-Type") - NSURLConnection.sendAsynchronousRequest(request, queue: self.queue, completionHandler:{ (response: NSURLResponse?, data: NSData?, error: NSError?) -> Void in - if let respHttp = response as? NSHTTPURLResponse { + NSURLConnection.sendAsynchronousRequest(request as URLRequest, queue: self.queue, completionHandler:{ (response: URLResponse?, data: Data?, error: NSError?) -> Void in + if let respHttp = response as? HTTPURLResponse { if (respHttp.statusCode >= 200 && respHttp.statusCode < 300) { resolver.result(ARHTTPResponse(code: jint(respHttp.statusCode), withContent: nil)) } else { @@ -52,7 +52,7 @@ class CocoaHttpRuntime: NSObject, ARHttpRuntime { } else { resolver.error(ARHTTPError(int: 0)) } - }) + } as! (URLResponse?, Data?, Error?) -> Void) } } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/CocoaLifecycleRuntime.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/CocoaLifecycleRuntime.swift index 8fd5255d9a..11a10e76c3 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/CocoaLifecycleRuntime.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/CocoaLifecycleRuntime.swift @@ -17,17 +17,17 @@ import Foundation @objc class CocoaWakeLock: NSObject, ARWakeLock { - private var background: UIBackgroundTaskIdentifier? + fileprivate var background: UIBackgroundTaskIdentifier? override init() { - background = UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler(nil) + background = UIApplication.shared.beginBackgroundTask(expirationHandler: nil) super.init() } - func releaseLock() { + public func close() { if background != nil { - UIApplication.sharedApplication().endBackgroundTask(background!) + UIApplication.shared.endBackgroundTask(background!) background = nil } } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/CocoaNetworkRuntime.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/CocoaNetworkRuntime.swift index 84249dc4f8..0af313ebf1 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/CocoaNetworkRuntime.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/CocoaNetworkRuntime.swift @@ -13,9 +13,9 @@ class CocoaNetworkRuntime : ARManagedNetworkProvider { class CocoaTcpConnectionFactory: NSObject, ARAsyncConnectionFactory { - func createConnectionWithConnectionId(connectionId: jint, - withEndpoint endpoint: ARConnectionEndpoint!, - withInterface connectionInterface: ARAsyncConnectionInterface!) -> ARAsyncConnection! { + func createConnection(withConnectionId connectionId: jint, + with endpoint: ARConnectionEndpoint!, + with connectionInterface: ARAsyncConnectionInterface!) -> ARAsyncConnection! { return CocoaTcpConnection(connectionId: Int(connectionId), endpoint: endpoint, connection: connectionInterface) } @@ -23,7 +23,7 @@ class CocoaTcpConnectionFactory: NSObject, ARAsyncConnectionFactory { class CocoaTcpConnection: ARAsyncConnection, GCDAsyncSocketDelegate { - static let queue = dispatch_queue_create("im.actor.network", DISPATCH_QUEUE_SERIAL) + static let queue = DispatchQueue(label: "im.actor.network", attributes: []) let READ_HEADER = 1 let READ_BODY = 2 @@ -31,10 +31,10 @@ class CocoaTcpConnection: ARAsyncConnection, GCDAsyncSocketDelegate { var TAG: String! var gcdSocket:GCDAsyncSocket? = nil - var header: NSData? + var header: Data? init(connectionId: Int, endpoint: ARConnectionEndpoint!, connection: ARAsyncConnectionInterface!) { - super.init(endpoint: endpoint, withInterface: connection) + super.init(endpoint: endpoint, with: connection) TAG = "🎍ConnectionTcp#\(connectionId)" } @@ -42,17 +42,17 @@ class CocoaTcpConnection: ARAsyncConnection, GCDAsyncSocketDelegate { let endpoint = getEndpoint() gcdSocket = GCDAsyncSocket(delegate: self, delegateQueue: CocoaTcpConnection.queue) - gcdSocket?.IPv4PreferredOverIPv6 = false + gcdSocket?.isIPv4PreferredOverIPv6 = false do { - try self.gcdSocket!.connectToHost(endpoint.host!, onPort: UInt16(endpoint.port), withTimeout: Double(ARManagedConnection_CONNECTION_TIMEOUT) / 1000.0) + try self.gcdSocket!.connect(toHost: (endpoint?.host!)!, onPort: UInt16((endpoint?.port)!), withTimeout: Double(ARManagedConnection_CONNECTION_TIMEOUT) / 1000.0) } catch _ { } } // Affer successful connection - func socket(sock: GCDAsyncSocket!, didConnectToHost host: String!, port: UInt16) { - if (self.getEndpoint().type == ARConnectionEndpoint.TYPE_TCP_TLS()) { + func socket(_ sock: GCDAsyncSocket!, didConnectToHost host: String!, port: UInt16) { + if (self.getEndpoint().type == ARConnectionEndpoint.type_TCP_TLS()) { // NSLog("\(TAG) Starring TLS Session...") sock.startTLS(nil) } else { @@ -61,39 +61,39 @@ class CocoaTcpConnection: ARAsyncConnection, GCDAsyncSocketDelegate { } // After TLS successful - func socketDidSecure(sock: GCDAsyncSocket!) { + func socketDidSecure(_ sock: GCDAsyncSocket!) { // NSLog("\(TAG) TLS Session started...") startConnection() } func startConnection() { - gcdSocket?.readDataToLength(UInt(9), withTimeout: -1, tag: READ_HEADER) + gcdSocket?.readData(toLength: UInt(9), withTimeout: -1, tag: READ_HEADER) onConnected() } // On connection closed - func socketDidDisconnect(sock: GCDAsyncSocket!, withError err: NSError!) { + func socketDidDisconnect(_ sock: GCDAsyncSocket!, withError err: NSError!) { // NSLog("\(TAG) Connection closed...") onClosed() } - func socket(sock: GCDAsyncSocket!, didReadData data: NSData!, withTag tag: Int) { + func socket(_ sock: GCDAsyncSocket!, didRead data: Data!, withTag tag: Int) { if (tag == READ_HEADER) { // NSLog("\(TAG) Header received") self.header = data data.readUInt32(0) // IGNORE: package id let size = data.readUInt32(5) - gcdSocket?.readDataToLength(UInt(size + 4), withTimeout: -1, tag: READ_BODY) + gcdSocket?.readData(toLength: UInt(size + 4), withTimeout: -1, tag: READ_BODY) } else if (tag == READ_BODY) { // NSLog("\(TAG) Body received") let package = NSMutableData() - package.appendData(self.header!) - package.appendData(data) - package.readUInt32(0) // IGNORE: package id + package.append(self.header!) + package.append(data) + // package.readUInt32(0) // IGNORE: package id self.header = nil - onReceived(package.toJavaBytes()) + // onReceived(package.toJavaBytes()) - gcdSocket?.readDataToLength(UInt(9), withTimeout: -1, tag: READ_HEADER) + gcdSocket?.readData(toLength: UInt(9), withTimeout: -1, tag: READ_HEADER) } else { fatalError("Unknown tag in read data") } @@ -107,22 +107,22 @@ class CocoaTcpConnection: ARAsyncConnection, GCDAsyncSocketDelegate { } } - override func doSend(data: IOSByteArray!) { - gcdSocket?.writeData(data.toNSData(), withTimeout: -1, tag: 0) + override func doSend(_ data: IOSByteArray!) { + gcdSocket?.write(data.toNSData(), withTimeout: -1, tag: 0) } } -private extension NSData { +private extension Data { func readUInt32() -> UInt32 { var raw: UInt32 = 0; - self.getBytes(&raw, length: 4) + (self as NSData).getBytes(&raw, length: 4) return raw.bigEndian } - func readUInt32(offset: Int) -> UInt32 { + func readUInt32(_ offset: Int) -> UInt32 { var raw: UInt32 = 0; - self.getBytes(&raw, range: NSMakeRange(offset, 4)) + (self as NSData).getBytes(&raw, range: NSMakeRange(offset, 4)) return raw.bigEndian } } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/CocoaStorageRuntime.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/CocoaStorageRuntime.swift index 18c18f7bbc..1f71580d0d 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/CocoaStorageRuntime.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/CocoaStorageRuntime.swift @@ -10,19 +10,19 @@ import Foundation let preferences = UDPreferencesStorage() override init() { - self.dbPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, - .UserDomainMask, true)[0].asNS.stringByAppendingPathComponent("actor.db") + self.dbPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, + .userDomainMask, true)[0].asNS.appendingPathComponent("actor.db") } func createPreferencesStorage() -> ARPreferencesStorage! { return preferences } - func createKeyValueWithName(name: String!) -> ARKeyValueStorage! { + func createKeyValue(withName name: String!) -> ARKeyValueStorage! { return FMDBKeyValue(databasePath: dbPath, tableName: name) } - func createListWithName(name: String!) -> ARListStorage! { + func createList(withName name: String!) -> ARListStorage! { return FMDBList(databasePath: dbPath, tableName: name) } @@ -30,8 +30,8 @@ import Foundation preferences.clear() let db = FMDatabase(path: dbPath) - db.open() - db.executeStatements("select 'drop table ' || name || ';' from sqlite_master where type = 'table';") - db.close() + db?.open() + db?.executeStatements("select 'drop table ' || name || ';' from sqlite_master where type = 'table';") + db?.close() } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/CocoaWebRTCRuntime.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/CocoaWebRTCRuntime.swift index fb2b21c19e..cbaece30f5 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/CocoaWebRTCRuntime.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/CocoaWebRTCRuntime.swift @@ -5,39 +5,39 @@ import Foundation import AVFoundation -let queue = dispatch_queue_create("My Queue", DISPATCH_QUEUE_SERIAL); +let queue = DispatchQueue(label: "My Queue", attributes: []); class CocoaWebRTCRuntime: NSObject, ARWebRTCRuntime { - private var isInited: Bool = false - private var peerConnectionFactory: RTCPeerConnectionFactory! - private var videoSource: RTCVideoSource! - private var videoSourceLoaded = false + fileprivate var isInited: Bool = false + fileprivate var peerConnectionFactory: RTCPeerConnectionFactory! + fileprivate var videoSource: RTCVideoSource! + fileprivate var videoSourceLoaded = false override init() { } - func getUserMediaWithIsAudioEnabled(isAudioEnabled: jboolean, withIsVideoEnabled isVideoEnabled: jboolean) -> ARPromise { + func getUserMedia(withIsAudioEnabled isAudioEnabled: jboolean, withIsVideoEnabled isVideoEnabled: jboolean) -> ARPromise { return ARPromise { (resolver) -> () in - dispatch_async(queue) { + queue.async { self.initRTC() // Unfortinatelly building capture source "on demand" causes some weird internal crashes self.initVideo() - let stream = self.peerConnectionFactory.mediaStreamWithLabel("ARDAMSv0") + let stream = self.peerConnectionFactory.mediaStream(withLabel: "ARDAMSv0") // // Audio // if isAudioEnabled { - let audio = self.peerConnectionFactory.audioTrackWithID("audio0") - stream.addAudioTrack(audio) + let audio = self.peerConnectionFactory.audioTrack(withID: "audio0") + stream?.addAudioTrack(audio) } // @@ -45,20 +45,20 @@ class CocoaWebRTCRuntime: NSObject, ARWebRTCRuntime { // if isVideoEnabled { if self.videoSource != nil { - let localVideoTrack = self.peerConnectionFactory.videoTrackWithID("video0", source: self.videoSource) - stream.addVideoTrack(localVideoTrack) + let localVideoTrack = self.peerConnectionFactory.videoTrack(withID: "video0", source: self.videoSource) + stream?.addVideoTrack(localVideoTrack) } } - resolver.result(MediaStream(stream:stream)) + resolver.result(MediaStream(stream:stream!)) } } } - func createPeerConnectionWithServers(webRTCIceServers: IOSObjectArray!, withSettings settings: ARWebRTCSettings!) -> ARPromise { + func createPeerConnection(withServers webRTCIceServers: IOSObjectArray!, with settings: ARWebRTCSettings!) -> ARPromise { let servers: [ARWebRTCIceServer] = webRTCIceServers.toSwiftArray() return ARPromise { (resolver) -> () in - dispatch_async(queue) { + queue.async { self.initRTC() resolver.result(CocoaWebRTCPeerConnection(servers: servers, peerConnectionFactory: self.peerConnectionFactory)) } @@ -78,15 +78,15 @@ class CocoaWebRTCRuntime: NSObject, ARWebRTCRuntime { self.videoSourceLoaded = true var cameraID: String? - for captureDevice in AVCaptureDevice.devicesWithMediaType(AVMediaTypeVideo) { - if captureDevice.position == AVCaptureDevicePosition.Front { - cameraID = captureDevice.localizedName + for captureDevice in AVCaptureDevice.devices(withMediaType: AVMediaTypeVideo) { + if (captureDevice as AnyObject).position == AVCaptureDevicePosition.front { + cameraID = (captureDevice as AnyObject).localizedName } } if(cameraID != nil) { let videoCapturer = RTCVideoCapturer(deviceName: cameraID) - self.videoSource = self.peerConnectionFactory.videoSourceWithCapturer(videoCapturer, constraints: RTCMediaConstraints()) + self.videoSource = self.peerConnectionFactory.videoSource(with: videoCapturer, constraints: RTCMediaConstraints()) } } } @@ -112,13 +112,13 @@ class CocoaWebRTCRuntime: NSObject, ARWebRTCRuntime { for i in 0.. jint { + open func getType() -> jint { return ARWebRTCTrackType_AUDIO } - public func setEnabledWithBoolean(isEnabled: jboolean) { + open func setEnabledWithBoolean(_ isEnabled: jboolean) { audioTrack.setEnabled(isEnabled) } - public func isEnabled() -> jboolean { + open func isEnabled() -> jboolean { return audioTrack.isEnabled() } } -public class CocoaVideoTrack: NSObject, ARWebRTCMediaTrack { +open class CocoaVideoTrack: NSObject, ARWebRTCMediaTrack { - public let videoTrack: RTCVideoTrack + open let videoTrack: RTCVideoTrack - public init(let videoTrack: RTCVideoTrack) { + public init(videoTrack: RTCVideoTrack) { self.videoTrack = videoTrack } - public func getTrackType() -> jint { + open func getType() -> jint { return ARWebRTCTrackType_VIDEO } - public func setEnabledWithBoolean(isEnabled: jboolean) { + open func setEnabledWithBoolean(_ isEnabled: jboolean) { videoTrack.setEnabled(isEnabled) } - public func isEnabled() -> jboolean { + open func isEnabled() -> jboolean { return videoTrack.isEnabled() } } class CocoaWebRTCPeerConnection: NSObject, ARWebRTCPeerConnection, RTCPeerConnectionDelegate { - private var peerConnection: RTCPeerConnection! - private var callbacks = [ARWebRTCPeerConnectionCallback]() - private let peerConnectionFactory: RTCPeerConnectionFactory - private var ownStreams = [ARCountedReference]() + fileprivate var peerConnection: RTCPeerConnection! + fileprivate var callbacks = [ARWebRTCPeerConnectionCallback]() + fileprivate let peerConnectionFactory: RTCPeerConnectionFactory + fileprivate var ownStreams = [ARCountedReference]() init(servers: [ARWebRTCIceServer], peerConnectionFactory: RTCPeerConnectionFactory) { self.peerConnectionFactory = peerConnectionFactory @@ -201,44 +201,44 @@ class CocoaWebRTCPeerConnection: NSObject, ARWebRTCPeerConnection, RTCPeerConnec let iceServers = servers.map { (src) -> RTCICEServer in if (src.username == nil || src.credential == nil) { - return RTCICEServer(URI: NSURL(string: src.url), username: "", password: "") + return RTCICEServer(uri: URL(string: src.url), username: "", password: "") } else { - return RTCICEServer(URI: NSURL(string: src.url), username: src.username, password: src.credential) + return RTCICEServer(uri: URL(string: src.url), username: src.username, password: src.credential) } } - peerConnection = peerConnectionFactory.peerConnectionWithICEServers(iceServers, constraints: RTCMediaConstraints(), delegate: self) + peerConnection = peerConnectionFactory.peerConnection(withICEServers: iceServers, constraints: RTCMediaConstraints(), delegate: self) AAAudioManager.sharedAudio().peerConnectionStarted() } - func addCallback(callback: ARWebRTCPeerConnectionCallback) { + func add(_ callback: ARWebRTCPeerConnectionCallback) { - if !callbacks.contains({ callback.isEqual($0) }) { + if !callbacks.contains(where: { callback.isEqual($0) }) { callbacks.append(callback) } } - func removeCallback(callback: ARWebRTCPeerConnectionCallback) { - let index = callbacks.indexOf({ callback.isEqual($0) }) + func remove(_ callback: ARWebRTCPeerConnectionCallback) { + let index = callbacks.index(where: { callback.isEqual($0) }) if index != nil { - callbacks.removeAtIndex(index!) + callbacks.remove(at: index!) } } - func addCandidateWithIndex(index: jint, withId id_: String, withSDP sdp: String) { - peerConnection.addICECandidate(RTCICECandidate(mid: id_, index: Int(index), sdp: sdp)) + func addCandidate(with index: jint, withId id_: String, withSDP sdp: String) { + peerConnection.add(RTCICECandidate(mid: id_, index: Int(index), sdp: sdp)) } - func addOwnStream(stream: ARCountedReference) { + func addOwnStream(_ stream: ARCountedReference) { stream.acquire() let ms = (stream.get() as! MediaStream) ownStreams.append(stream) - peerConnection.addStream(ms.stream) + peerConnection.add(ms.stream) } - func removeOwnStream(stream: ARCountedReference) { + func removeOwnStream(_ stream: ARCountedReference) { if ownStreams.contains(stream) { let ms = (stream.get() as! MediaStream) - peerConnection.removeStream(ms.stream) + peerConnection.remove(ms.stream) stream.release__() } } @@ -247,11 +247,11 @@ class CocoaWebRTCPeerConnection: NSObject, ARWebRTCPeerConnection, RTCPeerConnec return ARPromise(closure: { (resolver) -> () in let constraints = RTCMediaConstraints(mandatoryConstraints: [RTCPair(key: "OfferToReceiveAudio", value: "true"), RTCPair(key: "OfferToReceiveVideo", value: "true")], optionalConstraints: []) - self.peerConnection.createAnswer(constraints, didCreate: { (desc, error) -> () in + self.peerConnection.createAnswer(constraints!, didCreate: { (desc, error) -> () in if error == nil { - resolver.result(ARWebRTCSessionDescription(type: "answer", withSDP: desc.description)) + resolver.result(ARWebRTCSessionDescription(type: "answer", withSDP: desc!.description)) } else { - resolver.error(JavaLangException(NSString: "Error \(error.description)")) + resolver.error(JavaLangException(nsString: "Error \(error!)")) } }) }) @@ -261,35 +261,35 @@ class CocoaWebRTCPeerConnection: NSObject, ARWebRTCPeerConnection, RTCPeerConnec return ARPromise(closure: { (resolver) -> () in let constraints = RTCMediaConstraints(mandatoryConstraints: [RTCPair(key: "OfferToReceiveAudio", value: "true"), RTCPair(key: "OfferToReceiveVideo", value: "true")], optionalConstraints: []) - self.peerConnection.createOffer(constraints, didCreate: { (desc, error) -> () in + self.peerConnection.createOffer(constraints!, didCreate: { (desc, error) -> () in if error == nil { - resolver.result(ARWebRTCSessionDescription(type: "offer", withSDP: desc.description)) + resolver.result(ARWebRTCSessionDescription(type: "offer", withSDP: desc!.description)) } else { - resolver.error(JavaLangException(NSString: "Error \(error.description)")) + resolver.error(JavaLangException(nsString: "Error \(error!)")) } }) }) } - func setRemoteDescription(description_: ARWebRTCSessionDescription) -> ARPromise { + func setRemoteDescription(_ description_: ARWebRTCSessionDescription) -> ARPromise { return ARPromise(executor: AAPromiseFunc(closure: { (resolver) -> () in self.peerConnection.setRemoteDescription(RTCSessionDescription(type: description_.type, sdp: description_.sdp), didSet: { (error) -> () in if (error == nil) { resolver.result(description_) } else { - resolver.error(JavaLangException(NSString: "Error \(error.description)")) + resolver.error(JavaLangException(nsString: "Error \(error)")) } }) })) } - func setLocalDescription(description_: ARWebRTCSessionDescription) -> ARPromise { + func setLocalDescription(_ description_: ARWebRTCSessionDescription) -> ARPromise { return ARPromise(executor: AAPromiseFunc(closure: { (resolver) -> () in self.peerConnection.setLocalDescription(RTCSessionDescription(type: description_.type, sdp: description_.sdp), didSet: { (error) -> () in if (error == nil) { resolver.result(description_) } else { - resolver.error(JavaLangException(NSString: "Error \(error.description)")) + resolver.error(JavaLangException(nsString: "Error \(error)")) } }) })) @@ -299,7 +299,7 @@ class CocoaWebRTCPeerConnection: NSObject, ARWebRTCPeerConnection, RTCPeerConnec func close() { for s in ownStreams { let ms = s.get() as! MediaStream - peerConnection.removeStream(ms.stream) + peerConnection.remove(ms.stream) s.release__() } ownStreams.removeAll() @@ -312,43 +312,43 @@ class CocoaWebRTCPeerConnection: NSObject, ARWebRTCPeerConnection, RTCPeerConnec // - func peerConnection(peerConnection: RTCPeerConnection!, addedStream stream: RTCMediaStream!) { + func peerConnection(_ peerConnection: RTCPeerConnection!, addedStream stream: RTCMediaStream!) { for c in callbacks { c.onStreamAdded(MediaStream(stream: stream!)) } } - func peerConnection(peerConnection: RTCPeerConnection!, removedStream stream: RTCMediaStream!) { + func peerConnection(_ peerConnection: RTCPeerConnection!, removedStream stream: RTCMediaStream!) { for c in callbacks { c.onStreamRemoved(MediaStream(stream: stream!)) } } - func peerConnectionOnRenegotiationNeeded(peerConnection: RTCPeerConnection!) { + func peerConnection(onRenegotiationNeeded peerConnection: RTCPeerConnection!) { for c in callbacks { c.onRenegotiationNeeded() } } - func peerConnection(peerConnection: RTCPeerConnection!, gotICECandidate candidate: RTCICECandidate!) { + func peerConnection(_ peerConnection: RTCPeerConnection!, gotICECandidate candidate: RTCICECandidate!) { for c in callbacks { - c.onCandidateWithLabel(jint(candidate.sdpMLineIndex), withId: candidate.sdpMid, withCandidate: candidate.sdp) + c.onCandidate(withLabel: jint(candidate.sdpMLineIndex), withId: candidate.sdpMid, withCandidate: candidate.sdp) } } - func peerConnection(peerConnection: RTCPeerConnection!, signalingStateChanged stateChanged: RTCSignalingState) { + func peerConnection(_ peerConnection: RTCPeerConnection!, signalingStateChanged stateChanged: RTCSignalingState) { } - func peerConnection(peerConnection: RTCPeerConnection!, iceConnectionChanged newState: RTCICEConnectionState) { + func peerConnection(_ peerConnection: RTCPeerConnection!, iceConnectionChanged newState: RTCICEConnectionState) { } - func peerConnection(peerConnection: RTCPeerConnection!, iceGatheringChanged newState: RTCICEGatheringState) { + func peerConnection(_ peerConnection: RTCPeerConnection!, iceGatheringChanged newState: RTCICEGatheringState) { } - func peerConnection(peerConnection: RTCPeerConnection!, didOpenDataChannel dataChannel: RTCDataChannel!) { + func peerConnection(_ peerConnection: RTCPeerConnection!, didOpen dataChannel: RTCDataChannel!) { } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/Storage/FMDBBridge.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/Storage/FMDBBridge.swift index da1ad96917..addc8b685e 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/Storage/FMDBBridge.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/Storage/FMDBBridge.swift @@ -9,32 +9,33 @@ extension ARListEngineRecord { if (self.getQuery() == nil) { return NSNull() } else { - return self.getQuery()!.lowercaseString + return self.getQuery()!.lowercased() as AnyObject } } } extension jlong { func toNSNumber() -> NSNumber { - return NSNumber(longLong: self) + return NSNumber(value: self as Int64) } } extension jint { func toNSNumber() -> NSNumber { - return NSNumber(int: self) + return NSNumber(value: self as Int32) } } extension JavaLangLong { func toNSNumber() -> NSNumber { - return NSNumber(longLong: self.longLongValue()) + return NSNumber(value: self.longLongValue() as Int64) } } -extension NSData { +extension Data { - func readNSData(offset: Int, len: Int) -> NSData { - return self.subdataWithRange(NSMakeRange(Int(offset), Int(len))) - } -} \ No newline at end of file + func readNSData(_ offset: Int, len: Int) -> Data { + // return self.subdata(in: Int(offset)...Int(offset + len)) + return self + } +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/Storage/FMDBExtensions.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/Storage/FMDBExtensions.swift index 38ed67b36d..da320e070d 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/Storage/FMDBExtensions.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/Storage/FMDBExtensions.swift @@ -15,8 +15,8 @@ extension FMDatabase { /// /// :returns: This returns FMResultSet if successful. Returns nil upon error. - func executeQuery(sql:String, _ values: AnyObject...) -> FMResultSet? { - return executeQuery(sql, withArgumentsInArray: values); + func executeQuery(_ sql:String, _ values: AnyObject...) -> FMResultSet? { + return executeQuery(sql, withArgumentsIn: values); } /// This is a rendition of executeUpdate that handles Swift variadic parameters @@ -27,8 +27,8 @@ extension FMDatabase { /// /// :returns: This returns true if successful. Returns false upon error. - func executeUpdate(sql:String, _ values: AnyObject...) -> Bool { - return executeUpdate(sql, withArgumentsInArray: values); + func executeUpdate(_ sql:String, _ values: AnyObject...) -> Bool { + return executeUpdate(sql, withArgumentsIn: values); } /// Private generic function used for the variadic renditions of the FMDatabaseAdditions methods @@ -39,12 +39,12 @@ extension FMDatabase { /// /// :returns: This returns the T value if value is found. Returns nil if column is NULL or upon error. - private func valueForQuery(sql: String, values: NSArray?, completionHandler:(FMResultSet)->(T!)) -> T! { + fileprivate func valueForQuery(_ sql: String, values: NSArray?, completionHandler:(FMResultSet)->(T!)) -> T! { var result: T! - if let rs = executeQuery(sql, withArgumentsInArray: values! as [AnyObject]) { + if let rs = executeQuery(sql, withArgumentsIn: values! as [AnyObject]) { if rs.next() { - let obj = rs.objectForColumnIndex(0) as! NSObject + let obj = rs.object(forColumnIndex: 0) as! NSObject if !(obj is NSNull) { result = completionHandler(rs) } @@ -63,8 +63,8 @@ extension FMDatabase { /// /// :returns: This returns string value if value is found. Returns nil if column is NULL or upon error. - func stringForQuery(sql: String, _ values: AnyObject...) -> String! { - return valueForQuery(sql, values: values as NSArray) { $0.stringForColumnIndex(0) } + func stringForQuery(_ sql: String, _ values: AnyObject...) -> String! { + return valueForQuery(sql, values: values as NSArray) { $0.string(forColumnIndex: 0) } } /// This is a rendition of intForQuery that handles Swift variadic parameters @@ -75,8 +75,8 @@ extension FMDatabase { /// /// :returns: This returns integer value if value is found. Returns nil if column is NULL or upon error. - func intForQuery(sql: String, _ values: AnyObject...) -> Int32! { - return valueForQuery(sql, values: values as NSArray) { $0.intForColumnIndex(0) } + func intForQuery(_ sql: String, _ values: AnyObject...) -> Int32! { + return valueForQuery(sql, values: values as NSArray) { $0.int(forColumnIndex: 0) } } /// This is a rendition of longForQuery that handles Swift variadic parameters @@ -87,8 +87,8 @@ extension FMDatabase { /// /// :returns: This returns long value if value is found. Returns nil if column is NULL or upon error. - func longForQuery(sql: String, _ values: AnyObject...) -> Int! { - return valueForQuery(sql, values: values as NSArray) { $0.longForColumnIndex(0) } + func longForQuery(_ sql: String, _ values: AnyObject...) -> Int! { + return valueForQuery(sql, values: values as NSArray) { $0.long(forColumnIndex: 0) } } /// This is a rendition of boolForQuery that handles Swift variadic parameters @@ -99,8 +99,8 @@ extension FMDatabase { /// /// :returns: This returns Bool value if value is found. Returns nil if column is NULL or upon error. - func boolForQuery(sql: String, _ values: AnyObject...) -> Bool! { - return valueForQuery(sql, values: values as NSArray) { $0.boolForColumnIndex(0) } + func boolForQuery(_ sql: String, _ values: AnyObject...) -> Bool! { + return valueForQuery(sql, values: values as NSArray) { $0.bool(forColumnIndex: 0) } } /// This is a rendition of doubleForQuery that handles Swift variadic parameters @@ -111,8 +111,8 @@ extension FMDatabase { /// /// :returns: This returns Double value if value is found. Returns nil if column is NULL or upon error. - func doubleForQuery(sql: String, _ values: AnyObject...) -> Double! { - return valueForQuery(sql, values: values as NSArray) { $0.doubleForColumnIndex(0) } + func doubleForQuery(_ sql: String, _ values: AnyObject...) -> Double! { + return valueForQuery(sql, values: values as NSArray) { $0.double(forColumnIndex: 0) } } /// This is a rendition of dateForQuery that handles Swift variadic parameters @@ -123,8 +123,8 @@ extension FMDatabase { /// /// :returns: This returns NSDate value if value is found. Returns nil if column is NULL or upon error. - func dateForQuery(sql: String, _ values: AnyObject...) -> NSDate! { - return valueForQuery(sql, values: values as NSArray) { $0.dateForColumnIndex(0) } + func dateForQuery(_ sql: String, _ values: AnyObject...) -> Date! { + return valueForQuery(sql, values: values as NSArray) { $0.date(forColumnIndex: 0) } } /// This is a rendition of dataForQuery that handles Swift variadic parameters @@ -135,7 +135,7 @@ extension FMDatabase { /// /// :returns: This returns NSData value if value is found. Returns nil if column is NULL or upon error. - func dataForQuery(sql: String, _ values: AnyObject...) -> NSData! { - return valueForQuery(sql, values: values as NSArray) { $0.dataForColumnIndex(0) } + func dataForQuery(_ sql: String, _ values: AnyObject...) -> Data! { + return valueForQuery(sql, values: values as NSArray) { $0.data(forColumnIndex: 0) } } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/Storage/FMDBKeyValue.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/Storage/FMDBKeyValue.swift index 982e1ab713..f278a99831 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/Storage/FMDBKeyValue.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/Storage/FMDBKeyValue.swift @@ -40,7 +40,7 @@ import Foundation super.init() } - private func checkTable() { + fileprivate func checkTable() { if (isTableChecked) { return } @@ -53,37 +53,37 @@ import Foundation } } - func addOrUpdateItems(values: JavaUtilList!) { + func addOrUpdateItems(_ values: JavaUtilList!) { checkTable() db.beginTransaction() for i in 0.. IOSByteArray! { + func loadItem(withKey key: jlong) -> IOSByteArray! { checkTable() let result = db.dataForQuery(queryItem, key.toNSNumber()) if (result == nil) { return nil } - return result.toJavaBytes() + return result!.toJavaBytes() } func loadAllItems() -> JavaUtilList! { @@ -108,28 +108,28 @@ import Foundation if let result = db.executeQuery(queryAll) { while(result.next()) { - res.addWithId(ARKeyValueRecord(key: jlong(result.longLongIntForColumn("ID")), withData: result.dataForColumn("BYTES").toJavaBytes())) + res!.add(withId: ARKeyValueRecord(key: jlong(result.longLongInt(forColumn: "ID")), withData: result.data(forColumn: "BYTES").toJavaBytes())) } } return res } - func loadItems(keys: IOSLongArray!) -> JavaUtilList! { + func loadItems(_ keys: IOSLongArray!) -> JavaUtilList! { checkTable() // Converting to NSNumbers var ids = [NSNumber]() for i in 0.. 0 } else { @@ -178,7 +178,7 @@ class FMDBList : NSObject, ARListStorageDisplayEx { db!.commit() } - func loadItemWithKey(key: jlong) -> ARListEngineRecord! { + func loadItem(withKey key: jlong) -> ARListEngineRecord! { checkTable(); let result = db!.executeQuery(queryItem, key.toNSNumber()); @@ -186,11 +186,11 @@ class FMDBList : NSObject, ARListStorageDisplayEx { return nil } if (result!.next()) { - var query: AnyObject! = result!.objectForColumnName("QUERY"); + var query: AnyObject! = result!.object(forColumnName: "QUERY") as AnyObject!; if (query is NSNull){ query = nil } - let res = ARListEngineRecord(key: jlong(result!.longLongIntForColumn("ID")), withOrder: jlong(result!.longLongIntForColumn("SORT_KEY")), withQuery: query as! String?, withData: result!.dataForColumn("BYTES").toJavaBytes()) + let res = ARListEngineRecord(key: jlong(result!.longLongInt(forColumn: "ID")), withOrder: jlong(result!.longLongInt(forColumn: "SORT_KEY")), withQuery: query as! String?, withData: result!.data(forColumn: "BYTES").toJavaBytes()) result?.close() return res; } else { @@ -205,7 +205,7 @@ class FMDBList : NSObject, ARListStorageDisplayEx { return res } - func loadForwardWithSortKey(sortingKey: JavaLangLong!, withLimit limit: jint) -> JavaUtilList! { + func loadForward(withSortKey sortingKey: JavaLangLong!, withLimit limit: jint) -> JavaUtilList! { checkTable(); var result : FMResultSet? = nil; if (sortingKey == nil) { @@ -221,41 +221,41 @@ class FMDBList : NSObject, ARListStorageDisplayEx { let res: JavaUtilArrayList = JavaUtilArrayList(); - let queryIndex = result!.columnIndexForName("QUERY") - let idIndex = result!.columnIndexForName("ID") - let sortKeyIndex = result!.columnIndexForName("SORT_KEY") - let bytesIndex = result!.columnIndexForName("BYTES") + let queryIndex = result!.columnIndex(forName: "QUERY") + let idIndex = result!.columnIndex(forName: "ID") + let sortKeyIndex = result!.columnIndex(forName: "SORT_KEY") + let bytesIndex = result!.columnIndex(forName: "BYTES") var dataSize = 0 var rowCount = 0 while(result!.next()) { - let key = jlong(result!.longLongIntForColumnIndex(idIndex)) - let order = jlong(result!.longLongIntForColumnIndex(sortKeyIndex)) - var query: AnyObject! = result!.objectForColumnIndex(queryIndex) + let key = jlong(result!.longLongInt(forColumnIndex: idIndex)) + let order = jlong(result!.longLongInt(forColumnIndex: sortKeyIndex)) + var query: AnyObject! = result!.object(forColumnIndex: queryIndex) as AnyObject! if (query is NSNull) { query = nil } - let data = result!.dataForColumnIndex(bytesIndex).toJavaBytes() + let data = result!.data(forColumnIndex: bytesIndex).toJavaBytes() dataSize += Int(data.length()) rowCount += 1 let record = ARListEngineRecord(key: key, withOrder: order, withQuery: query as! String?, withData: data) - res.addWithId(record) + res.add(withId: record) } result!.close() return res; } - func loadForwardWithQuery(query: String!, withSortKey sortingKey: JavaLangLong!, withLimit limit: jint) -> JavaUtilList! { + func loadForward(withQuery query: String!, withSortKey sortingKey: JavaLangLong!, withLimit limit: jint) -> JavaUtilList! { checkTable(); var result : FMResultSet? = nil; - if (sortingKey == nil) { - result = db!.executeQuery(queryForwardFilterFirst, query + "%", "% " + query + "%", limit.toNSNumber()); - } else { - result = db!.executeQuery(queryForwardFilterMore, query + "%", "% " + query + "%", sortingKey!.toNSNumber(), limit.toNSNumber()); - } +// if (sortingKey == nil) { +// result = db!.executeQuery(queryForwardFilterFirst, query + "%", "% " + query + "%", limit.toNSNumber()); +// } else { +// result = db!.executeQuery(queryForwardFilterMore, query + "%", "% " + query + "%", sortingKey!.toNSNumber(), limit.toNSNumber()); +// } if (result == nil) { NSLog(db!.lastErrorMessage()) return nil @@ -264,12 +264,12 @@ class FMDBList : NSObject, ARListStorageDisplayEx { let res: JavaUtilArrayList = JavaUtilArrayList(); while(result!.next()) { - var query: AnyObject! = result!.objectForColumnName("QUERY"); + var query: AnyObject! = result!.object(forColumnName: "QUERY") as AnyObject!; if (query is NSNull) { query = nil } - let record = ARListEngineRecord(key: jlong(result!.longLongIntForColumn("ID")), withOrder: jlong(result!.longLongIntForColumn("SORT_KEY")), withQuery: query as! String?, withData: result!.dataForColumn("BYTES").toJavaBytes()) - res.addWithId(record) + let record = ARListEngineRecord(key: jlong(result!.longLongInt(forColumn: "ID")), withOrder: jlong(result!.longLongInt(forColumn: "SORT_KEY")), withQuery: query as! String?, withData: result!.data(forColumn: "BYTES").toJavaBytes()) + res.add(withId: record) } result!.close() @@ -277,7 +277,7 @@ class FMDBList : NSObject, ARListStorageDisplayEx { } - func loadBackwardWithSortKey(sortingKey: JavaLangLong!, withLimit limit: jint) -> JavaUtilList! { + func loadBackward(withSortKey sortingKey: JavaLangLong!, withLimit limit: jint) -> JavaUtilList! { checkTable(); var result : FMResultSet? = nil; if (sortingKey == nil) { @@ -293,26 +293,26 @@ class FMDBList : NSObject, ARListStorageDisplayEx { let res: JavaUtilArrayList = JavaUtilArrayList(); while(result!.next()) { - var query: AnyObject! = result!.objectForColumnName("QUERY"); + var query: AnyObject! = result!.object(forColumnName: "QUERY") as AnyObject!; if (query is NSNull) { query = nil } - let record = ARListEngineRecord(key: jlong(result!.longLongIntForColumn("ID")), withOrder: jlong(result!.longLongIntForColumn("SORT_KEY")), withQuery: query as! String?, withData: result!.dataForColumn("BYTES").toJavaBytes()) - res.addWithId(record) + let record = ARListEngineRecord(key: jlong(result!.longLongInt(forColumn: "ID")), withOrder: jlong(result!.longLongInt(forColumn: "SORT_KEY")), withQuery: query as! String?, withData: result!.data(forColumn: "BYTES").toJavaBytes()) + res.add(withId: record) } result!.close() return res; } - func loadBackwardWithQuery(query: String!, withSortKey sortingKey: JavaLangLong!, withLimit limit: jint) -> JavaUtilList! { + func loadBackward(withQuery query: String!, withSortKey sortingKey: JavaLangLong!, withLimit limit: jint) -> JavaUtilList! { checkTable(); var result : FMResultSet? = nil; - if (sortingKey == nil) { - result = db!.executeQuery(queryBackwardFilterFirst, query + "%", "% " + query + "%", limit.toNSNumber()); - } else { - result = db!.executeQuery(queryBackwardFilterMore, query + "%", "% " + query + "%", sortingKey!.toNSNumber(), limit.toNSNumber()); - } +// if (sortingKey == nil) { +// result = db!.executeQuery(queryBackwardFilterFirst, query + "%", "% " + query + "%", limit.toNSNumber()); +// } else { +// result = db!.executeQuery(queryBackwardFilterMore, query + "%", "% " + query + "%", sortingKey!.toNSNumber(), limit.toNSNumber()); +// } if (result == nil) { NSLog(db!.lastErrorMessage()) return nil @@ -321,28 +321,28 @@ class FMDBList : NSObject, ARListStorageDisplayEx { let res: JavaUtilArrayList = JavaUtilArrayList(); while(result!.next()) { - var query: AnyObject! = result!.objectForColumnName("QUERY"); + var query: AnyObject! = result!.object(forColumnName: "QUERY") as AnyObject!; if (query is NSNull) { query = nil } - let record = ARListEngineRecord(key: jlong(result!.longLongIntForColumn("ID")), withOrder: jlong(result!.longLongIntForColumn("SORT_KEY")), withQuery: query as! String?, withData: result!.dataForColumn("BYTES").toJavaBytes()) - res.addWithId(record) + let record = ARListEngineRecord(key: jlong(result!.longLongInt(forColumn: "ID")), withOrder: jlong(result!.longLongInt(forColumn: "SORT_KEY")), withQuery: query as! String?, withData: result!.data(forColumn: "BYTES").toJavaBytes()) + res.add(withId: record) } result!.close() return res; } - func loadCenterWithSortKey(centerSortKey: JavaLangLong!, withLimit limit: jint) -> JavaUtilList! { + func loadCenter(withSortKey centerSortKey: JavaLangLong!, withLimit limit: jint) -> JavaUtilList! { checkTable(); let res: JavaUtilArrayList = JavaUtilArrayList(); - res.addAllWithJavaUtilCollection(loadSlise(db!.executeQuery(queryCenterBackward, centerSortKey.toNSNumber(), limit.toNSNumber()))) - res.addAllWithJavaUtilCollection(loadSlise(db!.executeQuery(queryCenterForward, centerSortKey.toNSNumber(), limit.toNSNumber()))) + res.addAll(with: loadSlise(db!.executeQuery(queryCenterBackward, centerSortKey.toNSNumber(), limit.toNSNumber()))) + res.addAll(with: loadSlise(db!.executeQuery(queryCenterForward, centerSortKey.toNSNumber(), limit.toNSNumber()))) return res } - func loadSlise(result: FMResultSet?) -> JavaUtilList! { + func loadSlise(_ result: FMResultSet?) -> JavaUtilList! { if (result == nil) { NSLog(db!.lastErrorMessage()) return nil @@ -351,14 +351,14 @@ class FMDBList : NSObject, ARListStorageDisplayEx { let res: JavaUtilArrayList = JavaUtilArrayList(); while(result!.next()) { - var query: AnyObject! = result!.objectForColumnName("QUERY"); + var query: AnyObject! = result!.object(forColumnName: "QUERY") as AnyObject!; if (query is NSNull) { query = nil } - let record = ARListEngineRecord(key: jlong(result!.longLongIntForColumn("ID")), withOrder: jlong(result!.longLongIntForColumn("SORT_KEY")), withQuery: query as! String?, withData: result!.dataForColumn("BYTES").toJavaBytes()) - res.addWithId(record) + let record = ARListEngineRecord(key: jlong(result!.longLongInt(forColumn: "ID")), withOrder: jlong(result!.longLongInt(forColumn: "SORT_KEY")), withQuery: query as! String?, withData: result!.data(forColumn: "BYTES").toJavaBytes()) + res.add(withId: record) } result!.close() return res; } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/Storage/UDPreferencesStorage.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/Storage/UDPreferencesStorage.swift index 72d8e1d9a7..0ee7e3adba 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/Storage/UDPreferencesStorage.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/Storage/UDPreferencesStorage.swift @@ -6,41 +6,41 @@ import Foundation @objc class UDPreferencesStorage: NSObject, ARPreferencesStorage { - let prefs = NSUserDefaults.standardUserDefaults() + let prefs = UserDefaults.standard var cachedPrefs = [String: AnyObject?]() - func putLongWithKey(key: String!, withValue v: jlong) { - setObject(key, obj: NSNumber(longLong: v)) + func putLong(withKey key: String!, withValue v: jlong) { + setObject(key, obj: NSNumber(value: v as Int64)) } - func getLongWithKey(key: String!, withDefault def: jlong) -> jlong { + func getLongWithKey(_ key: String!, withDefault def: jlong) -> jlong { let val = fetchObj(key) if (val == nil || !(val is NSNumber)) { return def; } else { - return (val as! NSNumber).longLongValue + return (val as! NSNumber).int64Value } } - func putIntWithKey(key: String!, withValue v: jint) { - setObject(key, obj: Int(v)) + func putInt(withKey key: String!, withValue v: jint) { + setObject(key, obj: Int(v) as AnyObject?) } - func getIntWithKey(key: String!, withDefault def: jint) -> jint { + func getIntWithKey(_ key: String!, withDefault def: jint) -> jint { let val: AnyObject? = fetchObj(key) if (val == nil || !(val is NSNumber)) { return def; } else { - return (val as! NSNumber).intValue + return (val as! NSNumber).int32Value } } - func putBoolWithKey(key: String!, withValue v: Bool) { - setObject(key, obj: v) + func putBool(withKey key: String!, withValue v: Bool) { + setObject(key, obj: v as AnyObject?) } - func getBoolWithKey(key: String!, withDefault def: Bool) -> Bool { + func getBoolWithKey(_ key: String!, withDefault def: Bool) -> Bool { let val: AnyObject? = fetchObj(key); if (val == nil || (!(val is Bool))) { return def @@ -49,28 +49,28 @@ import Foundation } } - func putBytesWithKey(key: String!, withValue v: IOSByteArray!) { + func putBytes(withKey key: String!, withValue v: IOSByteArray!) { if (v == nil) { setObject(key, obj: nil) } else { - setObject(key, obj: v.toNSData()) + setObject(key, obj: v.toNSData() as AnyObject?) } } - func getBytesWithKey(key: String!) -> IOSByteArray! { + func getBytesWithKey(_ key: String!) -> IOSByteArray! { let val = fetchObj(key); if (val == nil || !(val is NSData)){ return nil } else { - return (val as! NSData).toJavaBytes() + return (val as! Data).toJavaBytes() } } - func putStringWithKey(key: String!, withValue v: String!) { - setObject(key, obj: v) + func putString(withKey key: String!, withValue v: String!) { + setObject(key, obj: v as AnyObject?) } - func getStringWithKey(key: String!) -> String! { + func getStringWithKey(_ key: String!) -> String! { let val = fetchObj(key); if (val == nil || !(val is String)) { return nil @@ -80,8 +80,8 @@ import Foundation } func clear() { - let appDomain = NSBundle.mainBundle().bundleIdentifier! - prefs.removePersistentDomainForName(appDomain) + let appDomain = Bundle.main.bundleIdentifier! + prefs.removePersistentDomain(forName: appDomain) } @@ -89,23 +89,23 @@ import Foundation // Interface // - private func setObject(key: String, obj: AnyObject?) { + fileprivate func setObject(_ key: String, obj: AnyObject?) { if obj != nil { - prefs.setObject(obj, forKey: key) + prefs.set(obj, forKey: key) cachedPrefs[key] = obj } else { - prefs.removeObjectForKey(key) - cachedPrefs.removeValueForKey(key) + prefs.removeObject(forKey: key) + cachedPrefs.removeValue(forKey: key) } prefs.synchronize() } - private func fetchObj(key: String) -> AnyObject? { + fileprivate func fetchObj(_ key: String) -> AnyObject? { if let obj = cachedPrefs[key] { return obj } - let res = prefs.objectForKey(key) - cachedPrefs[key] = res - return res + let res = prefs.object(forKey: key) + cachedPrefs[key] = res as AnyObject?? + return res as AnyObject? } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/iOSCallsProvider.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/iOSCallsProvider.swift index e2e1150555..574a232d28 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/iOSCallsProvider.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/iOSCallsProvider.swift @@ -11,27 +11,27 @@ class iOSCallsProvider: NSObject, ACCallsProvider { var ringtonePlayer: AVAudioPlayer! = nil var latestNotification: UILocalNotification! - func onCallStartWithCallId(callId: jlong) { + func onCallStart(withCallId callId: jlong) { AAAudioManager.sharedAudio().callStart(Actor.getCallWithCallId(callId)) dispatchOnUi() { let rootController = ActorSDK.sharedActor().bindedToWindow.rootViewController! if let presented = rootController.presentedViewController { - presented.dismissViewControllerAnimated(true, completion: { () -> Void in - rootController.presentViewController(AACallViewController(callId: callId), animated: true, completion: nil) + presented.dismiss(animated: true, completion: { () -> Void in + rootController.present(AACallViewController(callId: callId), animated: true, completion: nil) }) } else { - rootController.presentViewController(AACallViewController(callId: callId), animated: true, completion: nil) + rootController.present(AACallViewController(callId: callId), animated: true, completion: nil) } } } - func onCallAnsweredWithCallId(callId: jlong) { + func onCallAnswered(withCallId callId: jlong) { AAAudioManager.sharedAudio().callAnswered(Actor.getCallWithCallId(callId)) } - func onCallEndWithCallId(callId: jlong) { + func onCallEnd(withCallId callId: jlong) { AAAudioManager.sharedAudio().callEnd(Actor.getCallWithCallId(callId)) } @@ -39,7 +39,7 @@ class iOSCallsProvider: NSObject, ACCallsProvider { if (beepPlayer == nil) { do { - beepPlayer = try AVAudioPlayer(contentsOfURL: NSURL(fileURLWithPath: NSBundle.framework.pathForResource("tone", ofType: "m4a")!)) + beepPlayer = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: Bundle.framework.path(forResource: "tone", ofType: "m4a")!)) beepPlayer.prepareToPlay() beepPlayer.numberOfLoops = -1 } catch let error as NSError { @@ -56,4 +56,4 @@ class iOSCallsProvider: NSObject, ACCallsProvider { beepPlayer = nil } } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/iOSNotificationProvider.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/iOSNotificationProvider.swift index b7ea0f23e7..9924656eae 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/iOSNotificationProvider.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/iOSNotificationProvider.swift @@ -17,25 +17,25 @@ import AudioToolbox.AudioServices super.init() } - func loadSound(soundFile:String? = ""){ + func loadSound(_ soundFile:String? = ""){ if !isLoaded { isLoaded = true - var path = NSBundle.framework.URLForResource("notification", withExtension: "caf") + var path = Bundle.framework.url(forResource: "notification", withExtension: "caf") - if let fileURL: NSURL = NSURL(fileURLWithPath: "/Library/Ringtones/\(soundFile)") { + if let fileURL: URL = URL(fileURLWithPath: "/Library/Ringtones/\(soundFile)") { path = fileURL } - AudioServicesCreateSystemSoundID(path!, &internalMessage) + AudioServicesCreateSystemSoundID(path! as CFURL, &internalMessage) } } - func onMessageArriveInAppWithMessenger(messenger: ACMessenger!) { - let currentTime = NSDate().timeIntervalSinceReferenceDate + func onMessageArriveInApp(with messenger: ACMessenger!) { + let currentTime = Date().timeIntervalSinceReferenceDate if (currentTime - lastSoundPlay > 0.2) { - let peer = ACPeer.userWithInt(jint(messenger.myUid())) - let soundFileSting = messenger.getNotificationsSoundWithPeer(peer) + let peer = ACPeer.user(with: jint(messenger.myUid())) + let soundFileSting = messenger.getNotificationsSound(with: peer) loadSound(soundFileSting) AudioServicesPlaySystemSound(internalMessage) lastSoundPlay = currentTime @@ -43,11 +43,11 @@ import AudioToolbox.AudioServices } - func onNotificationWithMessenger(messenger: ACMessenger!, withTopNotifications topNotifications: JavaUtilList!, withMessagesCount messagesCount: jint, withConversationsCount conversationsCount: jint) { + func onNotification(with messenger: ACMessenger!, withTopNotifications topNotifications: JavaUtilList!, withMessagesCount messagesCount: jint, withConversationsCount conversationsCount: jint) { // Not Supported } - func onUpdateNotificationWithMessenger(messenger: ACMessenger!, withTopNotifications topNotifications: JavaUtilList!, withMessagesCount messagesCount: jint, withConversationsCount conversationsCount: jint) { + func onUpdateNotification(with messenger: ACMessenger!, withTopNotifications topNotifications: JavaUtilList!, withMessagesCount messagesCount: jint, withConversationsCount conversationsCount: jint) { // Not Supported } @@ -55,15 +55,15 @@ import AudioToolbox.AudioServices dispatchOnUi { () -> Void in // Clearing notifications if let number = Actor.getGlobalState().globalCounter.get() { - UIApplication.sharedApplication().applicationIconBadgeNumber = 0 // If current value will equals to number + 1 - UIApplication.sharedApplication().applicationIconBadgeNumber = number.integerValue + 1 - UIApplication.sharedApplication().applicationIconBadgeNumber = number.integerValue + UIApplication.shared.applicationIconBadgeNumber = 0 // If current value will equals to number + 1 + UIApplication.shared.applicationIconBadgeNumber = number.intValue + 1 + UIApplication.shared.applicationIconBadgeNumber = number.intValue } else { - UIApplication.sharedApplication().applicationIconBadgeNumber = 0 + UIApplication.shared.applicationIconBadgeNumber = 0 } // Clearing local notifications - UIApplication.sharedApplication().cancelAllLocalNotifications() + UIApplication.shared.cancelAllLocalNotifications() } } } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/iOSPhoneBookProvider.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/iOSPhoneBookProvider.swift index 435d39ff13..771416d65a 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/iOSPhoneBookProvider.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/iOSPhoneBookProvider.swift @@ -7,127 +7,127 @@ import AddressBook class PhoneBookProvider: NSObject, ACPhoneBookProvider { - func loadPhoneBookWithCallback(callback: ACPhoneBookProvider_Callback!) { + func loadPhoneBook(with callback: ACPhoneBookProvider_Callback!) { dispatchBackgroundDelayed(5.0) { let rawBook = ABAddressBookCreateWithOptions(nil, nil) if (rawBook == nil) { print("Access to AddressBook denied") - callback.onLoadedWithContacts(JavaUtilArrayList()) + callback.onLoaded(withContacts: JavaUtilArrayList()) return } - let book: ABAddressBook = rawBook.takeRetainedValue() - ABAddressBookRequestAccessWithCompletion(book, { (granted: Bool, error: CFError!) -> Void in - if (!granted) { - print("Access to AddressBook denied") - callback.onLoadedWithContacts(JavaUtilArrayList()) - return - } - - autoreleasepool { - let numbersSet = NSCharacterSet(charactersInString: "0123456789").invertedSet - let contacts = JavaUtilArrayList() - var index = 1 - let people = ABAddressBookCopyArrayOfAllPeople(book).takeRetainedValue() as [ABRecordRef] - - for person in people { - let firstName = self.extractString(person as ABRecord, propertyName: kABPersonFirstNameProperty) - let middleName = self.extractString(person as ABRecord, propertyName: kABPersonMiddleNameProperty) - let lastName = self.extractString(person as ABRecord, propertyName: kABPersonLastNameProperty) - - var contactName :String? - - // - // For Performance. LOL. - // - if firstName != nil { - if middleName != nil { - if lastName != nil { - contactName = firstName! + " " + middleName! + " " + lastName! - } else { - contactName = firstName! + " " + middleName! - } - } else { - if (lastName != nil) { - contactName = firstName! + " " + lastName! - } else { - contactName = firstName - } - } - } else { - if middleName != nil { - if lastName != nil { - contactName = middleName! + " " + lastName! - } else { - contactName = middleName - } - } else { - if lastName != nil { - contactName = lastName - } - } - } - - if (firstName == "Name not specified") { - contactName = nil - } - - let contactPhones = JavaUtilArrayList() - let contactEmails = JavaUtilArrayList() - let contact = ACPhoneBookContact(long: jlong(index), withNSString: contactName, withJavaUtilList: contactPhones, withJavaUtilList: contactEmails) - index += 1 - if let phones: ABMultiValueRef = - self.extractProperty(person as ABRecord, propertyName: kABPersonPhoneProperty) as ABMultiValueRef? { - for i in 0...ABMultiValueGetCount(phones) { - var phoneStr = self.extractString(phones, index: i) - if (phoneStr == nil || phoneStr!.trim().isEmpty) { - continue - } - phoneStr = phoneStr!.strip(numbersSet) - let phoneVal = Int64(phoneStr!)// numberFormatter.numberFromString(phoneStr!)?.longLongValue - if (phoneVal != nil) { - contactPhones.addWithId(ACPhoneBookPhone(long: jlong(index), withLong: jlong(phoneVal!))) - index += 1 - } - } - } - - if let emails: ABMultiValueRef = - self.extractProperty(person as ABRecord, propertyName: kABPersonEmailProperty) as ABMultiValueRef? { - for i in 0...ABMultiValueGetCount(emails) { - let emailStr = self.extractString(emails, index: i) - if (emailStr == nil || emailStr!.trim().isEmpty) { - continue - } - contactEmails.addWithId(ACPhoneBookEmail(long: jlong(index), withNSString: emailStr!)) - index += 1 - } - } - - if (contactPhones.size() != 0 || contactEmails.size() != 0) { - contacts.addWithId(contact) - } - } - - callback.onLoadedWithContacts(contacts) - } - }) + let book: ABAddressBook = rawBook!.takeRetainedValue() +// ABAddressBookRequestAccessWithCompletion(book, { (granted: Bool, error: CFError!) -> Void in +// if (!granted) { +// print("Access to AddressBook denied") +// callback.onLoaded(withContacts: JavaUtilArrayList()) +// return +// } +// +// autoreleasepool { +// let numbersSet = CharacterSet(charactersIn: "0123456789").inverted +// let contacts = JavaUtilArrayList() +// var index = 1 +// let people = ABAddressBookCopyArrayOfAllPeople(book).takeRetainedValue() as [ABRecordRef] +// +// for person in people { +// let firstName = self.extractString(person as ABRecord, propertyName: kABPersonFirstNameProperty) +// let middleName = self.extractString(person as ABRecord, propertyName: kABPersonMiddleNameProperty) +// let lastName = self.extractString(person as ABRecord, propertyName: kABPersonLastNameProperty) +// +// var contactName :String? +// +// // +// // For Performance. LOL. +// // +// if firstName != nil { +// if middleName != nil { +// if lastName != nil { +// contactName = firstName! + " " + middleName! + " " + lastName! +// } else { +// contactName = firstName! + " " + middleName! +// } +// } else { +// if (lastName != nil) { +// contactName = firstName! + " " + lastName! +// } else { +// contactName = firstName +// } +// } +// } else { +// if middleName != nil { +// if lastName != nil { +// contactName = middleName! + " " + lastName! +// } else { +// contactName = middleName +// } +// } else { +// if lastName != nil { +// contactName = lastName +// } +// } +// } +// +// if (firstName == "Name not specified") { +// contactName = nil +// } +// +// let contactPhones = JavaUtilArrayList() +// let contactEmails = JavaUtilArrayList() +// let contact = ACPhoneBookContact(long: jlong(index), with: contactName, with: contactPhones, with: contactEmails) +// index += 1 +// if let phones: ABMultiValueRef = +// self.extractProperty(person as ABRecord, propertyName: kABPersonPhoneProperty) as ABMultiValueRef? { +// for i in 0...ABMultiValueGetCount(phones) { +// var phoneStr = self.extractString(phones, index: i) +// if (phoneStr == nil || phoneStr!.trim().isEmpty) { +// continue +// } +// phoneStr = phoneStr!.strip(numbersSet) +// let phoneVal = Int64(phoneStr!)// numberFormatter.numberFromString(phoneStr!)?.longLongValue +// if (phoneVal != nil) { +// contactPhones.addWithId(ACPhoneBookPhone(long: jlong(index), withLong: jlong(phoneVal!))) +// index += 1 +// } +// } +// } +// +// if let emails: ABMultiValueRef = +// self.extractProperty(person as ABRecord, propertyName: kABPersonEmailProperty) as ABMultiValueRef? { +// for i in 0...ABMultiValueGetCount(emails) { +// let emailStr = self.extractString(emails, index: i) +// if (emailStr == nil || emailStr!.trim().isEmpty) { +// continue +// } +// contactEmails.addWithId(ACPhoneBookEmail(long: jlong(index), with: emailStr!)) +// index += 1 +// } +// } +// +// if (contactPhones.size() != 0 || contactEmails.size() != 0) { +// contacts.addWithId(contact) +// } +// } +// +// callback.onLoaded(withContacts: contacts) +// } +// }) } } - private func extractString(record: ABRecord, propertyName : ABPropertyID) -> String? { + fileprivate func extractString(_ record: ABRecord, propertyName : ABPropertyID) -> String? { return extractProperty(record, propertyName: propertyName) } - private func extractProperty(record: ABRecord, propertyName : ABPropertyID) -> T? { + fileprivate func extractProperty(_ record: ABRecord, propertyName : ABPropertyID) -> T? { //the following is two-lines of code for a reason. Do not combine (compiler optimization problems) let value: AnyObject? = ABRecordCopyValue(record, propertyName)?.takeRetainedValue() return value as? T } - private func extractString(record: ABMultiValueRef, index: Int) -> String? { + fileprivate func extractString(_ record: ABMultiValue, index: Int) -> String? { let value: AnyObject? = ABMultiValueCopyValueAtIndex(record, index)?.takeRetainedValue() return value as? String } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorSDK.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorSDK.swift index 0116857588..afdd9ab22b 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorSDK.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorSDK.swift @@ -8,15 +8,15 @@ import PushKit import SafariServices import DZNWebViewController -@objc public class ActorSDK: NSObject, PKPushRegistryDelegate { +@objc open class ActorSDK: NSObject, PKPushRegistryDelegate { // // Shared instance // - private static let shared = ActorSDK() + fileprivate static let shared = ActorSDK() - public static func sharedActor() -> ActorSDK { + open static func sharedActor() -> ActorSDK { return shared } @@ -25,23 +25,23 @@ import DZNWebViewController // /// Main Messenger object - public var messenger : ACCocoaMessenger! + open var messenger : ACCocoaMessenger! // Actor Style - public let style = ActorStyle() + open let style = ActorStyle() /// SDK Delegate - public var delegate: ActorSDKDelegate = ActorSDKDelegateDefault() + open var delegate: ActorSDKDelegate = ActorSDKDelegateDefault() /// SDK Analytics - public var analyticsDelegate: ActorSDKAnalytics? + open var analyticsDelegate: ActorSDKAnalytics? // // Configuration // /// Server Endpoints - public var endpoints = [ + open var endpoints = [ "tcp://front1-mtproto-api-rev3.actor.im:443", "tcp://front2-mtproto-api-rev3.actor.im:443" ] { @@ -51,7 +51,7 @@ import DZNWebViewController } /// Trusted Server Keys - public var trustedKeys = [ + open var trustedKeys = [ "d9d34ed487bd5b434eda2ef2c283db587c3ae7fb88405c3834d9d1a6d247145b", "4bd5422b50c585b5c8575d085e9fae01c126baa968dab56a396156759d5a7b46", "ff61103913aed3a9a689b6d77473bc428d363a3421fdd48a8e307a08e404f02c", @@ -61,98 +61,98 @@ import DZNWebViewController ] /// API ID - public var apiId = 2 + open var apiId = 2 /// API Key - public var apiKey = "2ccdc3699149eac0a13926c77ca84e504afd68b4f399602e06d68002ace965a3" + open var apiKey = "2ccdc3699149eac0a13926c77ca84e504afd68b4f399602e06d68002ace965a3" /// Push registration mode - public var autoPushMode = AAAutoPush.AfterLogin + open var autoPushMode = AAAutoPush.afterLogin /// Push token registration id. Required for sending push tokens - public var apiPushId: Int? = nil + open var apiPushId: Int? = nil /// Strategy about authentication - public var authStrategy = AAAuthStrategy.PhoneOnly + open var authStrategy = AAAuthStrategy.phoneOnly /// Enable phone book import - public var enablePhoneBookImport = true + open var enablePhoneBookImport = true /// Invitation URL for apps - public var inviteUrl: String = "https://actor.im/dl" + open var inviteUrl: String = "https://actor.im/dl" /// Invitation URL for apps - public var invitePrefix: String? = "https://actor.im/join/" + open var invitePrefix: String? = "https://actor.im/join/" /// Invitation URL for apps - public var invitePrefixShort: String? = "actor.im/join/" + open var invitePrefixShort: String? = "actor.im/join/" /// Privacy Policy URL - public var privacyPolicyUrl: String? = nil + open var privacyPolicyUrl: String? = nil /// Privacy Policy Text - public var privacyPolicyText: String? = nil + open var privacyPolicyText: String? = nil /// Terms of Service URL - public var termsOfServiceUrl: String? = nil + open var termsOfServiceUrl: String? = nil /// Terms of Service Text - public var termsOfServiceText: String? = nil + open var termsOfServiceText: String? = nil /// App name - public var appName: String = "Actor" + open var appName: String = "Actor" /// Use background on welcome screen - public var useBackgroundOnWelcomeScreen: Bool? = false + open var useBackgroundOnWelcomeScreen: Bool? = false /// Support email - public var supportEmail: String? = nil + open var supportEmail: String? = nil /// Support email - public var supportActivationEmail: String? = nil + open var supportActivationEmail: String? = nil /// Support account - public var supportAccount: String? = nil + open var supportAccount: String? = nil /// Support home page - public var supportHomepage: String? = "https://actor.im" + open var supportHomepage: String? = "https://actor.im" /// Support account - public var supportTwitter: String? = "actorapp" + open var supportTwitter: String? = "actorapp" /// Invite url scheme - public var inviteUrlScheme: String? = nil + open var inviteUrlScheme: String? = nil /// Web Invite Domain host - public var inviteUrlHost: String? = nil + open var inviteUrlHost: String? = nil /// Enable voice calls feature - public var enableCalls: Bool = false + open var enableCalls: Bool = false /// Enable video calls feature - public var enableVideoCalls: Bool = false + open var enableVideoCalls: Bool = false /// Enable custom sound on Groups and Chats - public var enableChatGroupSound: Bool = false + open var enableChatGroupSound: Bool = false /// Enable experimental features - public var enableExperimentalFeatures: Bool = false + open var enableExperimentalFeatures: Bool = false /// Auto Join Groups - public var autoJoinGroups = [String]() + open var autoJoinGroups = [String]() /// Should perform auto join only after first message or contact - public var autoJoinOnReady = true + open var autoJoinOnReady = true // // User Onlines // /// Is User online - private(set) public var isUserOnline = false + fileprivate(set) open var isUserOnline = false /// Disable this if you want manually handle online states - public var automaticOnlineHandling = true + open var automaticOnlineHandling = true // @@ -160,35 +160,35 @@ import DZNWebViewController // // Local Shared Settings - private static var udStorage = UDPreferencesStorage() + fileprivate static var udStorage = UDPreferencesStorage() - public var isPhotoAutoDownloadGroup: Bool = udStorage.getBoolWithKey("local.photo_download.group", withDefault: true) { + open var isPhotoAutoDownloadGroup: Bool = udStorage.getBoolWithKey("local.photo_download.group", withDefault: true) { willSet(v) { - ActorSDK.udStorage.putBoolWithKey("local.photo_download.group", withValue: v) + ActorSDK.udStorage.putBool(withKey: "local.photo_download.group", withValue: v) } } - public var isPhotoAutoDownloadPrivate: Bool = udStorage.getBoolWithKey("local.photo_download.private", withDefault: true) { + open var isPhotoAutoDownloadPrivate: Bool = udStorage.getBoolWithKey("local.photo_download.private", withDefault: true) { willSet(v) { - ActorSDK.udStorage.putBoolWithKey("local.photo_download.private", withValue: v) + ActorSDK.udStorage.putBool(withKey: "local.photo_download.private", withValue: v) } } - public var isAudioAutoDownloadGroup: Bool = udStorage.getBoolWithKey("local.audio_download.group", withDefault: true) { + open var isAudioAutoDownloadGroup: Bool = udStorage.getBoolWithKey("local.audio_download.group", withDefault: true) { willSet(v) { - ActorSDK.udStorage.putBoolWithKey("local.audio_download.group", withValue: v) + ActorSDK.udStorage.putBool(withKey: "local.audio_download.group", withValue: v) } } - public var isAudioAutoDownloadPrivate: Bool = udStorage.getBoolWithKey("local.audio_download.private", withDefault: true) { + open var isAudioAutoDownloadPrivate: Bool = udStorage.getBoolWithKey("local.audio_download.private", withDefault: true) { willSet(v) { - ActorSDK.udStorage.putBoolWithKey("local.audio_download.private", withValue: v) + ActorSDK.udStorage.putBool(withKey: "local.audio_download.private", withValue: v) } } - public var isGIFAutoplayEnabled: Bool = udStorage.getBoolWithKey("local.autoplay_gif", withDefault: true) { + open var isGIFAutoplayEnabled: Bool = udStorage.getBoolWithKey("local.autoplay_gif", withDefault: true) { willSet(v) { - ActorSDK.udStorage.putBoolWithKey("local.autoplay_gif", withValue: v) + ActorSDK.udStorage.putBool(withKey: "local.autoplay_gif", withValue: v) } } @@ -198,27 +198,27 @@ import DZNWebViewController // /// Is Actor Started - private(set) public var isStarted = false + fileprivate(set) open var isStarted = false - private var binder = AABinder() - private var syncTask: UIBackgroundTaskIdentifier? - private var completionHandler: ((UIBackgroundFetchResult) -> Void)? + fileprivate var binder = AABinder() + fileprivate var syncTask: UIBackgroundTaskIdentifier? + fileprivate var completionHandler: ((UIBackgroundFetchResult) -> Void)? // View Binding info - private(set) public var bindedToWindow: UIWindow! + fileprivate(set) open var bindedToWindow: UIWindow! // Reachability - private var reachability: Reachability! + // fileprivate var reachability: Reachability! public override init() { // Auto Loading Application name - if let name = NSBundle.mainBundle().objectForInfoDictionaryKey(String(kCFBundleNameKey)) as? String { + if let name = Bundle.main.object(forInfoDictionaryKey: String(kCFBundleNameKey)) as? String { self.appName = name } } - public func createActor() { + open func createActor() { if isStarted { return @@ -230,62 +230,62 @@ import DZNWebViewController let builder = ACConfigurationBuilder() // Api Connections - let deviceKey = NSUUID().UUIDString - let deviceName = UIDevice.currentDevice().name + let deviceKey = UUID().uuidString + let deviceName = UIDevice.current.name let appTitle = "Actor iOS" for url in endpoints { - builder.addEndpoint(url) + builder?.addEndpoint(url) } for key in trustedKeys { - builder.addTrustedKey(key) + builder?.addTrustedKey(key) } - builder.setApiConfiguration(ACApiConfiguration(appTitle: appTitle, withAppId: jint(apiId), withAppKey: apiKey, withDeviceTitle: deviceName, withDeviceId: deviceKey)) + builder?.setApiConfiguration(ACApiConfiguration(appTitle: appTitle, withAppId: jint(apiId), withAppKey: apiKey, withDeviceTitle: deviceName, withDeviceId: deviceKey)) // Providers - builder.setPhoneBookProvider(PhoneBookProvider()) - builder.setNotificationProvider(iOSNotificationProvider()) - builder.setCallsProvider(iOSCallsProvider()) + builder?.setPhoneBookProvider(PhoneBookProvider()) + builder?.setNotificationProvider(iOSNotificationProvider()) + builder?.setCallsProvider(iOSCallsProvider()) // Stats - builder.setPlatformType(ACPlatformType.IOS()) - builder.setDeviceCategory(ACDeviceCategory.MOBILE()) + builder?.setPlatformType(ACPlatformType.ios()) + builder?.setDeviceCategory(ACDeviceCategory.mobile()) // Locale - for lang in NSLocale.preferredLanguages() { + for lang in Locale.preferredLanguages { log("Found locale :\(lang)") - builder.addPreferredLanguage(lang) + builder?.addPreferredLanguage(lang) } // TimeZone - let timeZone = NSTimeZone.defaultTimeZone().name + let timeZone = TimeZone.current.identifier log("Found time zone :\(timeZone)") - builder.setTimeZone(timeZone) + builder?.setTimeZone(timeZone) // AutoJoin for s in autoJoinGroups { - builder.addAutoJoinGroupWithToken(s) + builder?.addAutoJoinGroup(withToken: s) } if autoJoinOnReady { - builder.setAutoJoinType(ACAutoJoinType.AFTER_INIT()) + builder?.setAutoJoinType(ACAutoJoinType.after_INIT()) } else { - builder.setAutoJoinType(ACAutoJoinType.IMMEDIATELY()) + builder?.setAutoJoinType(ACAutoJoinType.immediately()) } // Logs // builder.setEnableFilesLogging(true) // Application name - builder.setCustomAppName(appName) + builder?.setCustomAppName(appName) // Config - builder.setPhoneBookImportEnabled(jboolean(enablePhoneBookImport)) - builder.setVoiceCallsEnabled(jboolean(enableCalls)) - builder.setVideoCallsEnabled(jboolean(enableCalls)) - builder.setIsEnabledGroupedChatList(false) + builder?.setPhoneBookImportEnabled(jboolean(enablePhoneBookImport)) + builder?.setVoiceCallsEnabled(jboolean(enableCalls)) + builder?.setVideoCallsEnabled(jboolean(enableCalls)) + builder?.setIsEnabledGroupedChatList(false) // builder.setEnableFilesLogging(true) // Creating messenger - messenger = ACCocoaMessenger(configuration: builder.build()) + messenger = ACCocoaMessenger(configuration: (builder?.build())!) // Configure bubbles AABubbles.layouters = delegate.actorConfigureBubbleLayouters(AABubbles.builtInLayouters) @@ -297,17 +297,17 @@ import DZNWebViewController binder.bind(messenger.getGlobalState().isSyncing, closure: { (value: JavaLangBoolean?) -> () in if value!.booleanValue() { if self.syncTask == nil { - self.syncTask = UIApplication.sharedApplication().beginBackgroundTaskWithName("Background Sync", expirationHandler: { () -> Void in + self.syncTask = UIApplication.shared.beginBackgroundTask(withName: "Background Sync", expirationHandler: { () -> Void in }) } } else { if self.syncTask != nil { - UIApplication.sharedApplication().endBackgroundTask(self.syncTask!) + UIApplication.shared.endBackgroundTask(self.syncTask!) self.syncTask = nil } if self.completionHandler != nil { - self.completionHandler!(UIBackgroundFetchResult.NewData) + self.completionHandler!(UIBackgroundFetchResult.newData) self.completionHandler = nil } } @@ -317,43 +317,43 @@ import DZNWebViewController binder.bind(Actor.getGlobalState().globalCounter, closure: { (value: JavaLangInteger?) -> () in if let v = value { - UIApplication.sharedApplication().applicationIconBadgeNumber = Int(v.integerValue) + UIApplication.shared.applicationIconBadgeNumber = Int(v.intValue) } else { - UIApplication.sharedApplication().applicationIconBadgeNumber = 0 + UIApplication.shared.applicationIconBadgeNumber = 0 } }) // Push registration - if autoPushMode == .FromStart { + if autoPushMode == .fromStart { requestPush() } // Subscribe to network changes - do { - reachability = try Reachability.reachabilityForInternetConnection() - NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(ActorSDK.reachabilityChanged(_:)), name: ReachabilityChangedNotification, object: reachability) - try reachability.startNotifier() - } catch { - print("Unable to create Reachability") - return - } +// do { +// reachability = try Reachability.reachabilityForInternetConnection() +// NotificationCenter.default.addObserver(self, selector: #selector(ActorSDK.reachabilityChanged(_:)), name: NSNotification.Name(rawValue: ReachabilityChangedNotification), object: reachability) +// try reachability.startNotifier() +// } catch { +// print("Unable to create Reachability") +// return +// } } - @objc func reachabilityChanged(note: NSNotification) { - print("reachabilityChanged (\(reachability.isReachable()))") - - if reachability.isReachable() { - messenger.forceNetworkCheck() - } - } +// @objc func reachabilityChanged(_ note: Notification) { +// print("reachabilityChanged (\(reachability.isReachable()))") +// +// if reachability.isReachable() { +// messenger.forceNetworkCheck() +// } +// } func didLoggedIn() { // Push registration - if autoPushMode == .AfterLogin { + if autoPushMode == .afterLogin { requestPush() } @@ -388,56 +388,56 @@ import DZNWebViewController // /// Token need to be with stripped everything except numbers and letters - func pushRegisterToken(token: String) { + func pushRegisterToken(_ token: String) { if !isStarted { fatalError("Messenger not started") } if apiPushId != nil { - messenger.registerApplePushWithApnsId(jint(apiPushId!), withToken: token) + messenger.registerApplePush(withApnsId: jint(apiPushId!), withToken: token) } } - func pushRegisterKitToken(token: String) { + func pushRegisterKitToken(_ token: String) { if !isStarted { fatalError("Messenger not started") } if apiPushId != nil { - messenger.registerApplePushKitWithApnsId(jint(apiPushId!), withToken: token) + messenger.registerApplePushKit(withApnsId: jint(apiPushId!), withToken: token) } } - private func requestPush() { - let types: UIUserNotificationType = [.Alert, .Badge, .Sound] - let settings: UIUserNotificationSettings = UIUserNotificationSettings(forTypes: types, categories: nil) - UIApplication.sharedApplication().registerUserNotificationSettings(settings) - UIApplication.sharedApplication().registerForRemoteNotifications() + fileprivate func requestPush() { + let types: UIUserNotificationType = [.alert, .badge, .sound] + let settings: UIUserNotificationSettings = UIUserNotificationSettings(types: types, categories: nil) + UIApplication.shared.registerUserNotificationSettings(settings) + UIApplication.shared.registerForRemoteNotifications() } - private func requestPushKit() { - let voipRegistry = PKPushRegistry(queue: dispatch_get_main_queue()) + fileprivate func requestPushKit() { + let voipRegistry = PKPushRegistry(queue: DispatchQueue.main) voipRegistry.delegate = self - voipRegistry.desiredPushTypes = Set([PKPushTypeVoIP]) + voipRegistry.desiredPushTypes = Set([PKPushType.voIP]) } - @objc public func pushRegistry(registry: PKPushRegistry!, didUpdatePushCredentials credentials: PKPushCredentials!, forType type: String!) { - if (type == PKPushTypeVoIP) { + @objc open func pushRegistry(_ registry: PKPushRegistry, didUpdate credentials: PKPushCredentials, forType type: PKPushType) { + if (type == PKPushType.voIP) { let tokenString = "\(credentials.token)".replace(" ", dest: "").replace("<", dest: "").replace(">", dest: "") pushRegisterKitToken(tokenString) } } - @objc public func pushRegistry(registry: PKPushRegistry!, didInvalidatePushTokenForType type: String!) { - if (type == PKPushTypeVoIP) { + @objc open func pushRegistry(_ registry: PKPushRegistry, didInvalidatePushTokenForType type: PKPushType) { + if (type == PKPushType.voIP) { } } - @objc public func pushRegistry(registry: PKPushRegistry!, didReceiveIncomingPushWithPayload payload: PKPushPayload!, forType type: String!) { - if (type == PKPushTypeVoIP) { + @objc open func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, forType type: PKPushType) { + if (type == PKPushType.voIP) { let aps = payload.dictionaryPayload["aps"] as! [NSString: AnyObject] if let callId = aps["callId"] as? String { if let attempt = aps["attemptIndex"] as? String { @@ -446,13 +446,13 @@ import DZNWebViewController Actor.checkCall(jlong(callId)!, withAttempt: 0) } } else if let seq = aps["seq"] as? String { - Actor.onPushReceivedWithSeq(jint(seq)!) + // Actor.onPushReceivedWithSeq(jint(seq)!) } } } /// Get main navigations with check in delegate for customize from SDK - private func getMainNavigations() -> [AANavigationController] { + fileprivate func getMainNavigations() -> [AANavigationController] { let allControllers = self.delegate.actorRootControllers() @@ -509,7 +509,7 @@ import DZNWebViewController // Presenting Messenger // - public func presentMessengerInWindow(window: UIWindow) { + open func presentMessengerInWindow(_ window: UIWindow) { if !isStarted { fatalError("Messenger not started") } @@ -518,7 +518,7 @@ import DZNWebViewController if messenger.isLoggedIn() { - if autoPushMode == .AfterLogin { + if autoPushMode == .afterLogin { requestPush() } @@ -556,8 +556,8 @@ import DZNWebViewController if !style.statusBarConnectingHidden { JDStatusBarNotification.setDefaultStyle { (style) -> JDStatusBarStyle! in - style.barColor = self.style.statusBarConnectingBgColor - style.textColor = self.style.statusBarConnectingTextColor + style?.barColor = self.style.statusBarConnectingBgColor + style?.textColor = self.style.statusBarConnectingTextColor return style } @@ -567,9 +567,9 @@ import DZNWebViewController if isSyncing!.booleanValue() || isConnecting!.booleanValue() { if isConnecting!.booleanValue() { - JDStatusBarNotification.showWithStatus(AALocalized("StatusConnecting")) + JDStatusBarNotification.show(withStatus: AALocalized("StatusConnecting")) } else { - JDStatusBarNotification.showWithStatus(AALocalized("StatusSyncing")) + JDStatusBarNotification.show(withStatus: AALocalized("StatusSyncing")) } } else { JDStatusBarNotification.dismiss() @@ -579,9 +579,9 @@ import DZNWebViewController } } - public func presentMessengerInNewWindow() { - let window = UIWindow(frame: UIScreen.mainScreen().bounds); - window.backgroundColor = UIColor.whiteColor() + open func presentMessengerInNewWindow() { + let window = UIWindow(frame: UIScreen.main.bounds); + window.backgroundColor = UIColor.white presentMessengerInWindow(window) window.makeKeyAndVisible() } @@ -591,31 +591,30 @@ import DZNWebViewController // /// Handling URL Opening in application - func openUrl(url: String) { - if let u = NSURL(string: url) { + func openUrl(_ url: String) { + if let u = URL(string: url) { // Handle phone call - if (u.scheme.lowercaseString == "telprompt") { - UIApplication.sharedApplication().openURL(u) + if (u.scheme?.lowercased() == "telprompt") { + UIApplication.shared.openURL(u) return } // Handle web invite url - if (u.scheme.lowercaseString == "http" || u.scheme.lowercaseString == "https") && inviteUrlHost != nil { + if (u.scheme?.lowercased() == "http" || u.scheme?.lowercased() == "https") && inviteUrlHost != nil { if u.host == inviteUrlHost { - if let token = u.lastPathComponent { - joinGroup(token) - return - } + let token = u.lastPathComponent + joinGroup(token) + return } } // Handle custom scheme invite url - if (u.scheme.lowercaseString == inviteUrlScheme?.lowercaseString) { + if (u.scheme?.lowercased() == inviteUrlScheme?.lowercased()) { if (u.host == "invite") { - let token = u.query?.componentsSeparatedByString("=")[1] + let token = u.query?.components(separatedBy: "=")[1] if token != nil { joinGroup(token!) return @@ -623,9 +622,9 @@ import DZNWebViewController } if let bindedController = bindedToWindow?.rootViewController { - let alert = UIAlertController(title: nil, message: AALocalized("ErrorUnableToJoin"), preferredStyle: .Alert) - alert.addAction(UIAlertAction(title: AALocalized("AlertOk"), style: .Cancel, handler: nil)) - bindedController.presentViewController(alert, animated: true, completion: nil) + let alert = UIAlertController(title: nil, message: AALocalized("ErrorUnableToJoin"), preferredStyle: .alert) + alert.addAction(UIAlertAction(title: AALocalized("AlertOk"), style: .cancel, handler: nil)) + bindedController.present(alert, animated: true, completion: nil) } return @@ -638,67 +637,67 @@ import DZNWebViewController if let bindedController = bindedToWindow?.rootViewController { // Dismiss Old Presented Controller to show new one if let presented = bindedController.presentedViewController { - presented.dismissViewControllerAnimated(true, completion: nil) + presented.dismiss(animated: true, completion: nil) } // Building Controller for Web preview let controller: UIViewController if #available(iOS 9.0, *) { - controller = SFSafariViewController(URL: u) + controller = SFSafariViewController(url: u) } else { - controller = AANavigationController(rootViewController: DZNWebViewController(URL: u)) + controller = AANavigationController(rootViewController: DZNWebViewController(url: u)) } if AADevice.isiPad { - controller.modalPresentationStyle = .FullScreen + controller.modalPresentationStyle = .fullScreen } // Presenting controller - bindedController.presentViewController(controller, animated: true, completion: nil) + bindedController.present(controller, animated: true, completion: nil) } else { // Just Fallback. Might never happend - UIApplication.sharedApplication().openURL(u) + UIApplication.shared.openURL(u) } } } } /// Handling joining group by token - func joinGroup(token: String) { + func joinGroup(_ token: String) { if let bindedController = bindedToWindow?.rootViewController { - let alert = UIAlertController(title: nil, message: AALocalized("GroupJoinMessage"), preferredStyle: .Alert) - alert.addAction(UIAlertAction(title: AALocalized("AlertNo"), style: .Cancel, handler: nil)) - alert.addAction(UIAlertAction(title: AALocalized("GroupJoinAction"), style: .Default){ (action) -> Void in - AAExecutions.execute(Actor.joinGroupViaLinkCommandWithToken(token), type: .Safe, ignore: [], successBlock: { (val) -> Void in + let alert = UIAlertController(title: nil, message: AALocalized("GroupJoinMessage"), preferredStyle: .alert) + alert.addAction(UIAlertAction(title: AALocalized("AlertNo"), style: .cancel, handler: nil)) + alert.addAction(UIAlertAction(title: AALocalized("GroupJoinAction"), style: .default){ (action) -> Void in + AAExecutions.execute(Actor.joinGroupViaLinkCommand(withToken: token), type: .safe, ignore: [], successBlock: { (val) -> Void in // TODO: Fix for iPad let groupId = val as! JavaLangInteger let tabBarController = bindedController as! UITabBarController let index = tabBarController.selectedIndex let navController = tabBarController.viewControllers![index] as! UINavigationController - if let customController = ActorSDK.sharedActor().delegate.actorControllerForConversation(ACPeer.groupWithInt(groupId.intValue)) { + if let customController = ActorSDK.sharedActor().delegate.actorControllerForConversation(ACPeer.group(with: groupId.int32Value)) { navController.pushViewController(customController, animated: true) } else { - navController.pushViewController(ConversationViewController(peer: ACPeer.groupWithInt(groupId.intValue)), animated: true) + navController.pushViewController(ConversationViewController(peer: ACPeer.group(with: groupId.int32Value)), animated: true) } }, failureBlock: nil) }) - bindedController.presentViewController(alert, animated: true, completion: nil) + bindedController.present(alert, animated: true, completion: nil) } } /// Tracking page visible - func trackPageVisible(page: ACPage) { + func trackPageVisible(_ page: ACPage) { analyticsDelegate?.analyticsPageVisible(page) } /// Tracking page hidden - func trackPageHidden(page: ACPage) { + func trackPageHidden(_ page: ACPage) { analyticsDelegate?.analyticsPageHidden(page) } /// Tracking event - func trackEvent(event: ACEvent) { + func trackEvent(_ event: ACEvent) { analyticsDelegate?.analyticsEvent(event) } @@ -706,7 +705,7 @@ import DZNWebViewController // File System // - public func fullFilePathForDescriptor(descriptor: String) -> String { + open func fullFilePathForDescriptor(_ descriptor: String) -> String { return CocoaFiles.pathFromDescriptor(descriptor) } @@ -714,7 +713,7 @@ import DZNWebViewController // Manual Online handling // - public func didBecameOnline() { + open func didBecameOnline() { if automaticOnlineHandling { fatalError("Manual Online handling not enabled!") @@ -730,7 +729,7 @@ import DZNWebViewController } } - public func didBecameOffline() { + open func didBecameOffline() { if automaticOnlineHandling { fatalError("Manual Online handling not enabled!") } @@ -750,7 +749,7 @@ import DZNWebViewController // func checkAppState() { - if UIApplication.sharedApplication().applicationState == .Active { + if UIApplication.shared.applicationState == .active { if !isUserOnline { isUserOnline = true @@ -782,28 +781,28 @@ import DZNWebViewController } } - public func applicationDidFinishLaunching(application: UIApplication) { + open func applicationDidFinishLaunching(_ application: UIApplication) { if !automaticOnlineHandling || !isStarted { return } checkAppState() } - public func applicationDidBecomeActive(application: UIApplication) { + open func applicationDidBecomeActive(_ application: UIApplication) { if !automaticOnlineHandling || !isStarted { return } checkAppState() } - public func applicationWillEnterForeground(application: UIApplication) { + open func applicationWillEnterForeground(_ application: UIApplication) { if !automaticOnlineHandling || !isStarted { return } checkAppState() } - public func applicationDidEnterBackground(application: UIApplication) { + open func applicationDidEnterBackground(_ application: UIApplication) { if !automaticOnlineHandling || !isStarted { return } @@ -813,20 +812,20 @@ import DZNWebViewController if messenger.isLoggedIn() { var completitionTask: UIBackgroundTaskIdentifier = UIBackgroundTaskInvalid - completitionTask = application.beginBackgroundTaskWithName("Completition", expirationHandler: { () -> Void in + completitionTask = application.beginBackgroundTask(withName: "Completition", expirationHandler: { () -> Void in application.endBackgroundTask(completitionTask) completitionTask = UIBackgroundTaskInvalid }) // Wait for 40 secs before app shutdown - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(40.0 * Double(NSEC_PER_SEC))), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) { () -> Void in + DispatchQueue.global(priority: DispatchQueue.GlobalQueuePriority.default).asyncAfter(deadline: DispatchTime.now() + Double(Int64(40.0 * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC)) { () -> Void in application.endBackgroundTask(completitionTask) completitionTask = UIBackgroundTaskInvalid } } } - public func applicationWillResignActive(application: UIApplication) { + open func applicationWillResignActive(_ application: UIApplication) { if !automaticOnlineHandling || !isStarted { return } @@ -844,21 +843,21 @@ import DZNWebViewController // Handling remote notifications // - public func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) { + open func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { if !messenger.isLoggedIn() { - completionHandler(UIBackgroundFetchResult.NoData) + completionHandler(UIBackgroundFetchResult.noData) return } self.completionHandler = completionHandler } - public func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) { + open func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any]) { // Nothing? } - public func application(application: UIApplication, didRegisterUserNotificationSettings notificationSettings: UIUserNotificationSettings) { + open func application(_ application: UIApplication, didRegisterUserNotificationSettings notificationSettings: UIUserNotificationSettings) { requestPushKit() } @@ -866,10 +865,10 @@ import DZNWebViewController // Handling background fetch events // - public func application(application: UIApplication, performFetchWithCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) { + open func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { if !messenger.isLoggedIn() { - completionHandler(UIBackgroundFetchResult.NoData) + completionHandler(UIBackgroundFetchResult.noData) return } self.completionHandler = completionHandler @@ -879,7 +878,7 @@ import DZNWebViewController // Handling invite url // - func application(application: UIApplication, openURL url: NSURL, sourceApplication: String?, annotation: AnyObject) -> Bool { + func application(_ application: UIApplication, openURL url: URL, sourceApplication: String?, annotation: AnyObject) -> Bool { dispatchOnUi { () -> Void in self.openUrl(url.absoluteString) @@ -888,7 +887,7 @@ import DZNWebViewController return true } - public func application(application: UIApplication, handleOpenURL url: NSURL) -> Bool { + open func application(_ application: UIApplication, handleOpenURL url: URL) -> Bool { dispatchOnUi { () -> Void in self.openUrl(url.absoluteString) @@ -899,13 +898,13 @@ import DZNWebViewController } public enum AAAutoPush { - case None - case FromStart - case AfterLogin + case none + case fromStart + case afterLogin } public enum AAAuthStrategy { - case PhoneOnly - case EmailOnly - case PhoneEmail -} \ No newline at end of file + case phoneOnly + case emailOnly + case phoneEmail +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorSDKAnalytics.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorSDKAnalytics.swift index 317c859c25..03088fa416 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorSDKAnalytics.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorSDKAnalytics.swift @@ -7,11 +7,11 @@ import Foundation public protocol ActorSDKAnalytics { /// Called when page visible - func analyticsPageVisible(page: ACPage) + func analyticsPageVisible(_ page: ACPage) /// Called when page hidden - func analyticsPageHidden(page: ACPage) + func analyticsPageHidden(_ page: ACPage) /// Called when event occurs - func analyticsEvent(event: ACEvent) -} \ No newline at end of file + func analyticsEvent(_ event: ACEvent) +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorSDKDelegate.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorSDKDelegate.swift index d51a7edb8d..e303b1e088 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorSDKDelegate.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorSDKDelegate.swift @@ -17,13 +17,13 @@ public protocol ActorSDKDelegate { func actorControllerAfterLogIn() -> UIViewController? /// User profile controller - func actorControllerForUser(uid: Int) -> AAViewController? + func actorControllerForUser(_ uid: Int) -> AAViewController? /// User profile controller - func actorControllerForGroup(gid: Int) -> AAViewController? + func actorControllerForGroup(_ gid: Int) -> AAViewController? /// Conversation controller - func actorControllerForConversation(peer: ACPeer) -> UIViewController? + func actorControllerForConversation(_ peer: ACPeer) -> UIViewController? /// Contacts controller func actorControllerForContacts() -> UIViewController? @@ -41,99 +41,99 @@ public protocol ActorSDKDelegate { func actorRootInitialControllerIndex() -> Int? /// Configuration of bubble cells - func actorConfigureBubbleLayouters(builtIn: [AABubbleLayouter]) -> [AABubbleLayouter] + func actorConfigureBubbleLayouters(_ builtIn: [AABubbleLayouter]) -> [AABubbleLayouter] /// Conversation custom attach menu - func actorConversationCustomAttachMenu(controller: UIViewController) -> Bool + func actorConversationCustomAttachMenu(_ controller: UIViewController) -> Bool /// Called after header is created in settings page - func actorSettingsHeaderDidCreated(controller: AASettingsViewController, section: AAManagedSection) + func actorSettingsHeaderDidCreated(_ controller: AASettingsViewController, section: AAManagedSection) /// Called after header is created in settings page - func actorSettingsConfigurationWillCreated(controller: AASettingsViewController, section: AAManagedSection) + func actorSettingsConfigurationWillCreated(_ controller: AASettingsViewController, section: AAManagedSection) /// Called after header is created in settings page - func actorSettingsConfigurationDidCreated(controller: AASettingsViewController, section: AAManagedSection) + func actorSettingsConfigurationDidCreated(_ controller: AASettingsViewController, section: AAManagedSection) /// Called after header is created in settings page - func actorSettingsSupportWillCreated(controller: AASettingsViewController, section: AAManagedSection) + func actorSettingsSupportWillCreated(_ controller: AASettingsViewController, section: AAManagedSection) /// Called after header is created in settings page - func actorSettingsSupportDidCreated(controller: AASettingsViewController, section: AAManagedSection) + func actorSettingsSupportDidCreated(_ controller: AASettingsViewController, section: AAManagedSection) } /// Default empty implementation of SDK Delegate -public class ActorSDKDelegateDefault: NSObject, ActorSDKDelegate { +open class ActorSDKDelegateDefault: NSObject, ActorSDKDelegate { - public func actorControllerForAuthStart() -> UIViewController? { + open func actorControllerForAuthStart() -> UIViewController? { return nil } - public func actorControllerForStart() -> UIViewController? { + open func actorControllerForStart() -> UIViewController? { return nil } - public func actorControllerForUser(uid: Int) -> AAViewController? { + open func actorControllerForUser(_ uid: Int) -> AAViewController? { return nil } - public func actorControllerForGroup(gid: Int) -> AAViewController? { + open func actorControllerForGroup(_ gid: Int) -> AAViewController? { return nil } - public func actorControllerForConversation(peer: ACPeer) -> UIViewController? { + open func actorControllerForConversation(_ peer: ACPeer) -> UIViewController? { return nil } - public func actorControllerForContacts() -> UIViewController? { + open func actorControllerForContacts() -> UIViewController? { return nil } - public func actorControllerForDialogs() -> UIViewController? { + open func actorControllerForDialogs() -> UIViewController? { return nil } - public func actorControllerForSettings() -> UIViewController? { + open func actorControllerForSettings() -> UIViewController? { return nil } - public func actorRootControllers() -> [UIViewController]? { + open func actorRootControllers() -> [UIViewController]? { return nil } - public func actorRootInitialControllerIndex() -> Int? { + open func actorRootInitialControllerIndex() -> Int? { return nil } - public func actorConfigureBubbleLayouters(builtIn: [AABubbleLayouter]) -> [AABubbleLayouter] { + open func actorConfigureBubbleLayouters(_ builtIn: [AABubbleLayouter]) -> [AABubbleLayouter] { return builtIn } - public func actorControllerAfterLogIn() -> UIViewController? { + open func actorControllerAfterLogIn() -> UIViewController? { return nil } - public func actorConversationCustomAttachMenu(controller: UIViewController) -> Bool { + open func actorConversationCustomAttachMenu(_ controller: UIViewController) -> Bool { return false } - public func actorSettingsHeaderDidCreated(controller: AASettingsViewController, section: AAManagedSection) { + open func actorSettingsHeaderDidCreated(_ controller: AASettingsViewController, section: AAManagedSection) { } - public func actorSettingsConfigurationWillCreated(controller: AASettingsViewController, section: AAManagedSection) { + open func actorSettingsConfigurationWillCreated(_ controller: AASettingsViewController, section: AAManagedSection) { } - public func actorSettingsConfigurationDidCreated(controller: AASettingsViewController, section: AAManagedSection) { + open func actorSettingsConfigurationDidCreated(_ controller: AASettingsViewController, section: AAManagedSection) { } - public func actorSettingsSupportWillCreated(controller: AASettingsViewController, section: AAManagedSection) { + open func actorSettingsSupportWillCreated(_ controller: AASettingsViewController, section: AAManagedSection) { } - public func actorSettingsSupportDidCreated(controller: AASettingsViewController, section: AAManagedSection) { + open func actorSettingsSupportDidCreated(_ controller: AASettingsViewController, section: AAManagedSection) { } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorStyle.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorStyle.swift index 807674f9b6..5bf8b5e8bb 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorStyle.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorStyle.swift @@ -5,147 +5,147 @@ import Foundation import YYImage -public class ActorStyle { +open class ActorStyle { // // Main colors of app // /// Is Application have dark theme. Default is false. - public var isDarkApp = false + open var isDarkApp = false /// Tint Color. Star button - public var vcStarButton = UIColor(red: 75/255.0, green: 110/255.0, blue: 152/255.0, alpha: 1) + open var vcStarButton = UIColor(red: 75/255.0, green: 110/255.0, blue: 152/255.0, alpha: 1) /// Tint Color. Used for "Actions". Default is sytem blue. - public var vcTintColor = UIColor(rgb: 0x247dc7) + open var vcTintColor = UIColor(rgb: 0x247dc7) /// Color of desctructive actions. Default is red - public var vcDestructiveColor = UIColor.redColor() + open var vcDestructiveColor = UIColor.red /// Default background color - public var vcDefaultBackgroundColor = UIColor.whiteColor() + open var vcDefaultBackgroundColor = UIColor.white /// Main Text color of app - public var vcTextColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0xDE/255.0) + open var vcTextColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0xDE/255.0) /// Text Hint colors - public var vcHintColor = UIColor(red: 164/255.0, green: 164/255.0, blue: 164/255.0, alpha: 1) + open var vcHintColor = UIColor(red: 164/255.0, green: 164/255.0, blue: 164/255.0, alpha: 1) /// App's main status bar style. Default is light content. - public var vcStatusBarStyle = UIStatusBarStyle.Default + open var vcStatusBarStyle = UIStatusBarStyle.default /// UITableView separator color. Also used for other separators or borders. - public var vcSeparatorColor = UIColor(rgb: 0xdededf) + open var vcSeparatorColor = UIColor(rgb: 0xdededf) /// Cell Selected color - public var vcSelectedColor = UIColor(rgb: 0xd9d9d9) + open var vcSelectedColor = UIColor(rgb: 0xd9d9d9) /// Header/Footer text color - public var vcSectionColor = UIColor(rgb: 0x5b5a60) + open var vcSectionColor = UIColor(rgb: 0x5b5a60) /// Pacgkround of various panels like UITabBar. Default is white. - public var vcPanelBgColor = UIColor.whiteColor() + open var vcPanelBgColor = UIColor.white /// UISwitch off border color - public var vcSwitchOff = UIColor(rgb: 0xe6e6e6) + open var vcSwitchOff = UIColor(rgb: 0xe6e6e6) /// UISwitch on color - public var vcSwitchOn = UIColor(rgb: 0x4bd863) + open var vcSwitchOn = UIColor(rgb: 0x4bd863) /// View Controller background color - public var vcBgColor = UIColor.whiteColor() + open var vcBgColor = UIColor.white /// View Controller background color for settings - public var vcBackyardColor = UIColor(rgb: 0xf0eff5) + open var vcBackyardColor = UIColor(rgb: 0xf0eff5) // // UINavigationBar // /// Main Navigation bar color - public var navigationBgColor: UIColor = UIColor(red: 247.0/255.0, green: 247.0/255.0, blue: 247.0/255.0, alpha: 1) + open var navigationBgColor: UIColor = UIColor(red: 247.0/255.0, green: 247.0/255.0, blue: 247.0/255.0, alpha: 1) /// Main Navigation bar hairline color - public var navigationHairlineHidden = false + open var navigationHairlineHidden = false /// Navigation Bar icons colors - public var navigationTintColor: UIColor = UIColor(rgb: 0x5085CB) + open var navigationTintColor: UIColor = UIColor(rgb: 0x5085CB) /// Navigation Bar title color - public var navigationTitleColor: UIColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0xDE/255.0) + open var navigationTitleColor: UIColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0xDE/255.0) /// Navigation Bar subtitle color, default is 0.8 alhpa of navigationTitleColor - public var navigationSubtitleColor: UIColor { + open var navigationSubtitleColor: UIColor { get { return _navigationSubtitleColor != nil ? _navigationSubtitleColor! : navigationTitleColor.alpha(0.8) } set(v) { _navigationSubtitleColor = v } } - private var _navigationSubtitleColor: UIColor? + fileprivate var _navigationSubtitleColor: UIColor? /// Navigation Bar actove subtitle color, default is navigationTitleColor - public var navigationSubtitleActiveColor: UIColor { + open var navigationSubtitleActiveColor: UIColor { get { return _navigationSubtitleActiveColor != nil ? _navigationSubtitleActiveColor! : navigationTitleColor } set(v) { _navigationSubtitleActiveColor = v } } - private var _navigationSubtitleActiveColor: UIColor? + fileprivate var _navigationSubtitleActiveColor: UIColor? // // Token Field. Used at entering members of new group. // /// Token Text Color. Default is vcTextColor. - public var vcTokenFieldTextColor: UIColor { + open var vcTokenFieldTextColor: UIColor { get { return _vcTokenFieldTextColor != nil ? _vcTokenFieldTextColor! : vcTextColor } set(v) { _vcTokenFieldTextColor = v } } - private var _vcTokenFieldTextColor: UIColor? + fileprivate var _vcTokenFieldTextColor: UIColor? /// Background Color of Token field. Default is vcBgColor. - public var vcTokenFieldBgColor: UIColor { + open var vcTokenFieldBgColor: UIColor { get { return _vcTokenFieldBgColor != nil ? _vcTokenFieldBgColor! : vcBgColor } set(v) { _vcTokenFieldBgColor = v } } - private var _vcTokenFieldBgColor: UIColor? + fileprivate var _vcTokenFieldBgColor: UIColor? /// Token Tint Color. Default is vcTintColor. - public var vcTokenTintColor: UIColor { + open var vcTokenTintColor: UIColor { get { return _vcTokenTintColor != nil ? _vcTokenTintColor! : vcTintColor } set(v) { _vcTokenTintColor = v } } - private var _vcTokenTintColor: UIColor? + fileprivate var _vcTokenTintColor: UIColor? // // Search style // /// Style of status bar when search is active. - public var searchStatusBarStyle = UIStatusBarStyle.Default + open var searchStatusBarStyle = UIStatusBarStyle.default /// Background Color of search bar - public var searchBackgroundColor: UIColor { - get { return _searchBackgroundColor != nil ? _searchBackgroundColor! : UIColor.whiteColor() } + open var searchBackgroundColor: UIColor { + get { return _searchBackgroundColor != nil ? _searchBackgroundColor! : UIColor.white } set(v) { _searchBackgroundColor = v } } - private var _searchBackgroundColor: UIColor? + fileprivate var _searchBackgroundColor: UIColor? /// Cancel button color - public var searchCancelColor: UIColor { + open var searchCancelColor: UIColor { get { return _searchCancelColor != nil ? _searchCancelColor! : vcTintColor } set(v) { _searchCancelColor = v } } - private var _searchCancelColor: UIColor? + fileprivate var _searchCancelColor: UIColor? /// Search Input Field background color - public var searchFieldBgColor = UIColor(rgb: 0xededed) + open var searchFieldBgColor = UIColor(rgb: 0xededed) /// Search Input Field text color - public var searchFieldTextColor = UIColor.blackColor().alpha(0.56) + open var searchFieldTextColor = UIColor.black.alpha(0.56) // // UITabBarView style // /// Selected Text Color of UITabViewItem. Default is vcTintColor. - public var tabSelectedTextColor: UIColor { + open var tabSelectedTextColor: UIColor { get { return _tabSelectedTextColor != nil ? _tabSelectedTextColor! : vcTintColor } set(v) { _tabSelectedTextColor = v } } - private var _tabSelectedTextColor: UIColor? + fileprivate var _tabSelectedTextColor: UIColor? /// Selected Icon Color of UITableViewItem. Default is vcTintColor. - public var tabSelectedIconColor: UIColor { + open var tabSelectedIconColor: UIColor { get { return _tabSelectedIconColor != nil ? _tabSelectedIconColor! : vcTintColor } set(v) { _tabSelectedIconColor = v } } - private var _tabSelectedIconColor: UIColor? + fileprivate var _tabSelectedIconColor: UIColor? /// Unselected Text Color of UITabViewItem. Default is vcHintColor. - public var tabUnselectedTextColor: UIColor { + open var tabUnselectedTextColor: UIColor { get { return _tabUnselectedTextColor != nil ? _tabUnselectedTextColor! : vcHintColor } set(v) { _tabUnselectedTextColor = v } } - private var _tabUnselectedTextColor: UIColor? + fileprivate var _tabUnselectedTextColor: UIColor? /// Unselected Icon Color of UITableViewItem. Default is vcHintColor. - private var _tabUnselectedIconColor: UIColor? - public var tabUnselectedIconColor: UIColor { + fileprivate var _tabUnselectedIconColor: UIColor? + open var tabUnselectedIconColor: UIColor { get { return _tabUnselectedIconColor != nil ? _tabUnselectedIconColor! : vcHintColor } set(v) { _tabUnselectedIconColor = v } } /// Background color of UITabBarView. Default is vcPanelBgColor. - private var _tabBgColor: UIColor? - public var tabBgColor: UIColor { + fileprivate var _tabBgColor: UIColor? + open var tabBgColor: UIColor { get { return _tabBgColor != nil ? _tabBgColor! : vcPanelBgColor } set(v) { _tabBgColor = v } } @@ -155,106 +155,106 @@ public class ActorStyle { // /// Cell Background color. Default is vcBgColor. - public var cellBgColor: UIColor { + open var cellBgColor: UIColor { get { return _cellBgColor != nil ? _cellBgColor! : vcBgColor } set(v) { _cellBgColor = v } } - private var _cellBgColor: UIColor? + fileprivate var _cellBgColor: UIColor? /// Cell Background selected color. Default is vcSelectedColor. - public var cellBgSelectedColor: UIColor { + open var cellBgSelectedColor: UIColor { get { return _cellBgSelectedColor != nil ? _cellBgSelectedColor! : vcSelectedColor } set(v) { _cellBgSelectedColor = v } } - private var _cellBgSelectedColor: UIColor? + fileprivate var _cellBgSelectedColor: UIColor? /// Cell text color. Default is vcTextColor. - public var cellTextColor: UIColor { + open var cellTextColor: UIColor { get { return _cellTextColor != nil ? _cellTextColor! : vcTextColor } set(v) { _cellTextColor = v } } - private var _cellTextColor: UIColor? + fileprivate var _cellTextColor: UIColor? /// Cell hint text color. Default is vcHintColor. - public var cellHintColor: UIColor { + open var cellHintColor: UIColor { get { return _cellHintColor != nil ? _cellHintColor! : vcHintColor } set(v) { _cellHintColor = v } } - private var _cellHintColor: UIColor? + fileprivate var _cellHintColor: UIColor? /// Cell action color. Default is vcTintColor. - public var cellTintColor: UIColor { + open var cellTintColor: UIColor { get { return _cellTintColor != nil ? _cellTintColor! : vcTintColor } set(v) { _cellTintColor = v } } - private var _cellTintColor: UIColor? + fileprivate var _cellTintColor: UIColor? /// Cell desctructive color. Default is vcDestructiveColor. - public var cellDestructiveColor: UIColor { + open var cellDestructiveColor: UIColor { get { return _cellDestructiveColor != nil ? _cellDestructiveColor! : vcDestructiveColor } set(v) { _cellDestructiveColor = v } } - private var _cellDestructiveColor: UIColor? + fileprivate var _cellDestructiveColor: UIColor? /// Section header color. Default is vcSectionColor. - public var cellHeaderColor: UIColor { + open var cellHeaderColor: UIColor { get { return _cellHeaderColor != nil ? _cellHeaderColor! : vcSectionColor } set(v) { _cellHeaderColor = v } } - private var _cellHeaderColor: UIColor? + fileprivate var _cellHeaderColor: UIColor? /// Section footer color. Default is vcSectionColor. - public var cellFooterColor: UIColor { + open var cellFooterColor: UIColor { get { return _cellFooterColor != nil ? _cellFooterColor! : vcSectionColor } set(v) { _cellFooterColor = v } } - private var _cellFooterColor: UIColor? + fileprivate var _cellFooterColor: UIColor? // // Full screen placeholder style // /// Big Placeholder background color - public var placeholderBgColor: UIColor { + open var placeholderBgColor: UIColor { get { return _placeholderBgColor != nil ? _placeholderBgColor! : navigationBgColor.fromTransparentBar() } set(v) { _placeholderBgColor = v } } - private var _placeholderBgColor: UIColor? + fileprivate var _placeholderBgColor: UIColor? /// Big placeholder title color - public var placeholderTitleColor: UIColor { + open var placeholderTitleColor: UIColor { get { return _placeholderTitleColor != nil ? _placeholderTitleColor! : vcTextColor } set(v) { _placeholderTitleColor = v } } - private var _placeholderTitleColor: UIColor? + fileprivate var _placeholderTitleColor: UIColor? /// Bit Placeholder hint color - public var placeholderHintColor: UIColor { + open var placeholderHintColor: UIColor { get { return _placeholderHintColor != nil ? _placeholderHintColor! : vcHintColor } set(v) { _placeholderHintColor = v } } - private var _placeholderHintColor: UIColor? + fileprivate var _placeholderHintColor: UIColor? // // Avatar Placeholder and name colors // - public var avatarTextColor = UIColor.whiteColor() + open var avatarTextColor = UIColor.white - public var avatarLightBlue = UIColor(rgb: 0x59b7d3) - public var nameLightBlue = UIColor(rgb: 0x59b7d3) + open var avatarLightBlue = UIColor(rgb: 0x59b7d3) + open var nameLightBlue = UIColor(rgb: 0x59b7d3) - public var avatarDarkBlue = UIColor(rgb: 0x1d4e6f) - public var nameDarkBlue = UIColor(rgb: 0x1d4e6f) + open var avatarDarkBlue = UIColor(rgb: 0x1d4e6f) + open var nameDarkBlue = UIColor(rgb: 0x1d4e6f) - public var avatarPurple = UIColor(rgb: 0x995794) - public var namePurple = UIColor(rgb: 0x995794) + open var avatarPurple = UIColor(rgb: 0x995794) + open var namePurple = UIColor(rgb: 0x995794) - public var avatarPink = UIColor(rgb: 0xff506c) - public var namePink = UIColor(rgb: 0xff506c) + open var avatarPink = UIColor(rgb: 0xff506c) + open var namePink = UIColor(rgb: 0xff506c) - public var avatarOrange = UIColor(rgb: 0xf99341) - public var nameOrange = UIColor(rgb: 0xf99341) + open var avatarOrange = UIColor(rgb: 0xf99341) + open var nameOrange = UIColor(rgb: 0xf99341) - public var avatarYellow = UIColor(rgb: 0xe4d027) - public var nameYellow = UIColor(rgb: 0xe4d027) + open var avatarYellow = UIColor(rgb: 0xe4d027) + open var nameYellow = UIColor(rgb: 0xe4d027) - public var avatarGreen = UIColor(rgb: 0xe4d027) - public var nameGreen = UIColor(rgb: 0xe4d027) + open var avatarGreen = UIColor(rgb: 0xe4d027) + open var nameGreen = UIColor(rgb: 0xe4d027) - private var _avatarColors: [UIColor]? - public var avatarColors: [UIColor] { + fileprivate var _avatarColors: [UIColor]? + open var avatarColors: [UIColor] { get { if _avatarColors == nil { return [ @@ -273,8 +273,8 @@ public class ActorStyle { set(v) { _avatarColors = v } } - private var _nameColors: [UIColor]? - public var nameColors: [UIColor] { + fileprivate var _nameColors: [UIColor]? + open var nameColors: [UIColor] { get { if _nameColors == nil { return [ @@ -299,329 +299,329 @@ public class ActorStyle { // Text colors - private var _chatTextColor: UIColor? - public var chatTextColor: UIColor { + fileprivate var _chatTextColor: UIColor? + open var chatTextColor: UIColor { get { return _chatTextColor != nil ? _chatTextColor! : vcTextColor } set(v) { _chatTextColor = v } } - private var _chatUrlColor: UIColor? - public var chatUrlColor: UIColor { + fileprivate var _chatUrlColor: UIColor? + open var chatUrlColor: UIColor { get { return _chatUrlColor != nil ? _chatUrlColor! : vcTintColor } set(v) { _chatUrlColor = v } } - private var _chatTextUnsupportedColor: UIColor? - public var chatTextUnsupportedColor: UIColor { + fileprivate var _chatTextUnsupportedColor: UIColor? + open var chatTextUnsupportedColor: UIColor { get { return _chatTextUnsupportedColor != nil ? _chatTextUnsupportedColor! : vcTintColor.alpha(0.54) } set(v) { _chatTextUnsupportedColor = v } } - private var _chatTextOutColor: UIColor? - public var chatTextOutColor: UIColor { + fileprivate var _chatTextOutColor: UIColor? + open var chatTextOutColor: UIColor { get { return _chatTextOutColor != nil ? _chatTextOutColor! : chatTextColor } set(v) { _chatTextOutColor = v } } - private var _chatTextInColor: UIColor? - public var chatTextInColor: UIColor { + fileprivate var _chatTextInColor: UIColor? + open var chatTextInColor: UIColor { get { return _chatTextInColor != nil ? _chatTextInColor! : chatTextColor } set(v) { _chatTextInColor = v } } - private var _chatTextOutUnsupportedColor: UIColor? - public var chatTextOutUnsupportedColor: UIColor { + fileprivate var _chatTextOutUnsupportedColor: UIColor? + open var chatTextOutUnsupportedColor: UIColor { get { return _chatTextOutUnsupportedColor != nil ? _chatTextOutUnsupportedColor! : chatTextUnsupportedColor } set(v) { _chatTextOutUnsupportedColor = v } } - private var _chatTextInUnsupportedColor: UIColor? - public var chatTextInUnsupportedColor: UIColor { + fileprivate var _chatTextInUnsupportedColor: UIColor? + open var chatTextInUnsupportedColor: UIColor { get { return _chatTextInUnsupportedColor != nil ? _chatTextInUnsupportedColor! : chatTextUnsupportedColor } set(v) { _chatTextInUnsupportedColor = v } } - public var chatDateTextColor = UIColor.whiteColor() + open var chatDateTextColor = UIColor.white - public var chatServiceTextColor = UIColor.whiteColor() + open var chatServiceTextColor = UIColor.white - public var chatUnreadTextColor = UIColor.whiteColor() + open var chatUnreadTextColor = UIColor.white // Date colors - public var chatTextDateOutColor = UIColor.alphaBlack(0.27) - public var chatTextDateInColor = UIColor(rgb: 0x979797) + open var chatTextDateOutColor = UIColor.alphaBlack(0.27) + open var chatTextDateInColor = UIColor(rgb: 0x979797) - public var chatMediaDateColor = UIColor.whiteColor() - public var chatMediaDateBgColor = UIColor.blackColor().alpha(0.4) + open var chatMediaDateColor = UIColor.white + open var chatMediaDateBgColor = UIColor.black.alpha(0.4) // Bubble Colors - public var chatTextBubbleOutColor = UIColor(rgb: 0xD2FEFD) + open var chatTextBubbleOutColor = UIColor(rgb: 0xD2FEFD) - public var chatTextBubbleOutSelectedColor = UIColor.lightGrayColor() + open var chatTextBubbleOutSelectedColor = UIColor.lightGray - public var chatTextBubbleOutBorderColor = UIColor(rgb: 0x99E4E3) + open var chatTextBubbleOutBorderColor = UIColor(rgb: 0x99E4E3) - public var chatTextBubbleInColor = UIColor.whiteColor() + open var chatTextBubbleInColor = UIColor.white - public var chatTextBubbleInSelectedColor = UIColor.blueColor() + open var chatTextBubbleInSelectedColor = UIColor.blue - public var chatTextBubbleInBorderColor = UIColor(rgb: 0xCCCCCC) + open var chatTextBubbleInBorderColor = UIColor(rgb: 0xCCCCCC) - public var chatMediaBubbleColor = UIColor.whiteColor() - public var chatMediaBubbleBorderColor = UIColor(rgb: 0xCCCCCC) + open var chatMediaBubbleColor = UIColor.white + open var chatMediaBubbleBorderColor = UIColor(rgb: 0xCCCCCC) - public var chatDateBubbleColor = UIColor(rgb: 0x2D394A, alpha: 0.56) + open var chatDateBubbleColor = UIColor(rgb: 0x2D394A, alpha: 0.56) - public var chatServiceBubbleColor = UIColor(rgb: 0x2D394A, alpha: 0.56) + open var chatServiceBubbleColor = UIColor(rgb: 0x2D394A, alpha: 0.56) - public var chatUnreadBgColor = UIColor.alphaBlack(0.3) + open var chatUnreadBgColor = UIColor.alphaBlack(0.3) - public var chatReadMediaColor = UIColor(red: 46.6/255.0, green: 211.3/255.0, blue: 253.6/255.0, alpha: 1.0) + open var chatReadMediaColor = UIColor(red: 46.6/255.0, green: 211.3/255.0, blue: 253.6/255.0, alpha: 1.0) // Bubble Shadow - public var bubbleShadowEnabled = false + open var bubbleShadowEnabled = false - public var chatTextBubbleShadowColor = UIColor.alphaBlack(0.1) + open var chatTextBubbleShadowColor = UIColor.alphaBlack(0.1) // Status Colors - public lazy var chatIconCheck1 = UIImage.templated("msg_check_1") - public lazy var chatIconCheck2 = UIImage.templated("msg_check_2") - public lazy var chatIconError = UIImage.templated("msg_error") - public lazy var chatIconWarring = UIImage.templated("msg_warring") - public lazy var chatIconClock = UIImage.templated("msg_clock") + open lazy var chatIconCheck1 = UIImage.templated("msg_check_1") + open lazy var chatIconCheck2 = UIImage.templated("msg_check_2") + open lazy var chatIconError = UIImage.templated("msg_error") + open lazy var chatIconWarring = UIImage.templated("msg_warring") + open lazy var chatIconClock = UIImage.templated("msg_clock") - private var _chatStatusActive: UIColor? - public var chatStatusActive: UIColor { + fileprivate var _chatStatusActive: UIColor? + open var chatStatusActive: UIColor { get { return _chatStatusActive != nil ? _chatStatusActive! : vcTintColor } set(v) { _chatStatusActive = v } } - private var _chatStatusPassive: UIColor? - public var chatStatusPassive: UIColor { + fileprivate var _chatStatusPassive: UIColor? + open var chatStatusPassive: UIColor { get { return _chatStatusPassive != nil ? _chatStatusPassive! : vcHintColor } set(v) { _chatStatusPassive = v } } - private var _chatStatusDanger: UIColor? - public var chatStatusDanger: UIColor { + fileprivate var _chatStatusDanger: UIColor? + open var chatStatusDanger: UIColor { get { return _chatStatusDanger != nil ? _chatStatusDanger! : vcDestructiveColor } set(v) { _chatStatusDanger = v } } - private var _chatStatusMediaActive: UIColor? - public var chatStatusMediaActive: UIColor { + fileprivate var _chatStatusMediaActive: UIColor? + open var chatStatusMediaActive: UIColor { get { return _chatStatusMediaActive != nil ? _chatStatusMediaActive! : chatReadMediaColor } set(v) { _chatStatusMediaActive = v } } - private var _chatStatusMediaPassive: UIColor? - public var chatStatusMediaPassive: UIColor { - get { return _chatStatusMediaPassive != nil ? _chatStatusMediaPassive! : UIColor.whiteColor() } + fileprivate var _chatStatusMediaPassive: UIColor? + open var chatStatusMediaPassive: UIColor { + get { return _chatStatusMediaPassive != nil ? _chatStatusMediaPassive! : UIColor.white } set(v) { _chatStatusMediaPassive = v } } - private var _chatStatusMediaDanger: UIColor? - public var chatStatusMediaDanger: UIColor { + fileprivate var _chatStatusMediaDanger: UIColor? + open var chatStatusMediaDanger: UIColor { get { return _chatStatusMediaDanger != nil ? _chatStatusMediaDanger! : chatStatusDanger } set(v) { _chatStatusMediaDanger = v } } - private var _chatStatusSending: UIColor? - public var chatStatusSending: UIColor { + fileprivate var _chatStatusSending: UIColor? + open var chatStatusSending: UIColor { get { return _chatStatusSending != nil ? _chatStatusSending! : chatStatusPassive } set(v) { _chatStatusSending = v } } - private var _chatStatusSent: UIColor? - public var chatStatusSent: UIColor { + fileprivate var _chatStatusSent: UIColor? + open var chatStatusSent: UIColor { get { return _chatStatusSent != nil ? _chatStatusSent! : chatStatusPassive } set(v) { _chatStatusSent = v } } - private var _chatStatusReceived: UIColor? - public var chatStatusReceived: UIColor { + fileprivate var _chatStatusReceived: UIColor? + open var chatStatusReceived: UIColor { get { return _chatStatusReceived != nil ? _chatStatusReceived! : chatStatusPassive } set(v) { _chatStatusReceived = v } } - private var _chatStatusRead: UIColor? - public var chatStatusRead: UIColor { + fileprivate var _chatStatusRead: UIColor? + open var chatStatusRead: UIColor { get { return _chatStatusRead != nil ? _chatStatusRead! : chatStatusActive } set(v) { _chatStatusRead = v } } - private var _chatStatusError: UIColor? - public var chatStatusError: UIColor { + fileprivate var _chatStatusError: UIColor? + open var chatStatusError: UIColor { get { return _chatStatusError != nil ? _chatStatusError! : chatStatusDanger } set(v) { _chatStatusError = v } } - private var _chatStatusMediaSending: UIColor? - public var chatStatusMediaSending: UIColor { + fileprivate var _chatStatusMediaSending: UIColor? + open var chatStatusMediaSending: UIColor { get { return _chatStatusMediaSending != nil ? _chatStatusMediaSending! : chatStatusMediaPassive } set(v) { _chatStatusMediaSending = v } } - private var _chatStatusMediaSent: UIColor? - public var chatStatusMediaSent: UIColor { + fileprivate var _chatStatusMediaSent: UIColor? + open var chatStatusMediaSent: UIColor { get { return _chatStatusMediaSent != nil ? _chatStatusMediaSent! : chatStatusMediaPassive } set(v) { _chatStatusMediaSent = v } } - private var _chatStatusMediaReceived: UIColor? - public var chatStatusMediaReceived: UIColor { + fileprivate var _chatStatusMediaReceived: UIColor? + open var chatStatusMediaReceived: UIColor { get { return _chatStatusMediaReceived != nil ? _chatStatusMediaReceived! : chatStatusMediaPassive } set(v) { _chatStatusMediaReceived = v } } - private var _chatStatusMediaRead: UIColor? - public var chatStatusMediaRead: UIColor { + fileprivate var _chatStatusMediaRead: UIColor? + open var chatStatusMediaRead: UIColor { get { return _chatStatusMediaRead != nil ? _chatStatusMediaRead! : chatStatusMediaActive } set(v) { _chatStatusMediaRead = v } } - private var _chatStatusMediaError: UIColor? - public var chatStatusMediaError: UIColor { + fileprivate var _chatStatusMediaError: UIColor? + open var chatStatusMediaError: UIColor { get { return _chatStatusMediaError != nil ? _chatStatusMediaError! : chatStatusMediaDanger } set(v) { _chatStatusMediaError = v } } // Chat screen - private var _chatInputField: UIColor? - public var chatInputFieldBgColor: UIColor { + fileprivate var _chatInputField: UIColor? + open var chatInputFieldBgColor: UIColor { get { return _chatInputField != nil ? _chatInputField! : vcPanelBgColor } set(v) { _chatInputField = v } } - private var _chatAttachColor: UIColor? - public var chatAttachColor: UIColor { + fileprivate var _chatAttachColor: UIColor? + open var chatAttachColor: UIColor { get { return _chatAttachColor != nil ? _chatAttachColor! : vcTintColor } set(v) { _chatAttachColor = v } } - private var _chatSendColor: UIColor? - public var chatSendColor: UIColor { + fileprivate var _chatSendColor: UIColor? + open var chatSendColor: UIColor { get { return _chatSendColor != nil ? _chatSendColor! : vcTintColor } set(v) { _chatSendColor = v } } - private var _chatSendDisabledColor: UIColor? - public var chatSendDisabledColor: UIColor { + fileprivate var _chatSendDisabledColor: UIColor? + open var chatSendDisabledColor: UIColor { get { return _chatSendDisabledColor != nil ? _chatSendDisabledColor! : vcTintColor.alpha(0.64) } set(v) { _chatSendDisabledColor = v } } - private var _chatAutocompleteHighlight: UIColor? - public var chatAutocompleteHighlight: UIColor { + fileprivate var _chatAutocompleteHighlight: UIColor? + open var chatAutocompleteHighlight: UIColor { get { return _chatAutocompleteHighlight != nil ? _chatAutocompleteHighlight! : vcTintColor } set(v) { _chatAutocompleteHighlight = v } } - public lazy var chatBgColor = UIColor(patternImage: UIImage.bundled("chat_bg")!) + open lazy var chatBgColor = UIColor(patternImage: UIImage.bundled("chat_bg")!) // // Dialogs styles // - private var _dialogTitleColor: UIColor? - public var dialogTitleColor: UIColor { + fileprivate var _dialogTitleColor: UIColor? + open var dialogTitleColor: UIColor { get { return _dialogTitleColor != nil ? _dialogTitleColor! : vcTextColor } set(v) { _dialogTitleColor = v } } - private var _dialogTextColor: UIColor? - public var dialogTextColor: UIColor { + fileprivate var _dialogTextColor: UIColor? + open var dialogTextColor: UIColor { get { return _dialogTextColor != nil ? _dialogTextColor! : dialogTitleColor.alpha(0.64) } set(v) { _dialogTextColor = v } } - private var _dialogTextActiveColor: UIColor? - public var dialogTextActiveColor: UIColor { + fileprivate var _dialogTextActiveColor: UIColor? + open var dialogTextActiveColor: UIColor { get { return _dialogTextActiveColor != nil ? _dialogTextActiveColor! : vcTextColor } set(v) { _dialogTextActiveColor = v } } - private var _dialogDateColor: UIColor? - public var dialogDateColor: UIColor { + fileprivate var _dialogDateColor: UIColor? + open var dialogDateColor: UIColor { get { return _dialogDateColor != nil ? _dialogDateColor! : vcHintColor } set(v) { _dialogDateColor = v } } - public var dialogCounterBgColor: UIColor = UIColor(rgb: 0x50A1D6) + open var dialogCounterBgColor: UIColor = UIColor(rgb: 0x50A1D6) - public var dialogCounterColor: UIColor = UIColor.whiteColor() + open var dialogCounterColor: UIColor = UIColor.white - private var _dialogStatusActive: UIColor? - public var dialogStatusActive: UIColor { + fileprivate var _dialogStatusActive: UIColor? + open var dialogStatusActive: UIColor { get { return _dialogStatusActive != nil ? _dialogStatusActive! : chatStatusActive } set(v) { _dialogStatusActive = v } } - private var _dialogStatusPassive: UIColor? - public var dialogStatusPassive: UIColor { + fileprivate var _dialogStatusPassive: UIColor? + open var dialogStatusPassive: UIColor { get { return _dialogStatusPassive != nil ? _dialogStatusPassive! : chatStatusPassive } set(v) { _dialogStatusPassive = v } } - private var _dialogStatusDanger: UIColor? - public var dialogStatusDanger: UIColor { + fileprivate var _dialogStatusDanger: UIColor? + open var dialogStatusDanger: UIColor { get { return _dialogStatusDanger != nil ? _dialogStatusDanger! : chatStatusDanger } set(v) { _dialogStatusDanger = v } } - private var _dialogStatusSending: UIColor? - public var dialogStatusSending: UIColor { + fileprivate var _dialogStatusSending: UIColor? + open var dialogStatusSending: UIColor { get { return _dialogStatusSending != nil ? _dialogStatusSending! : dialogStatusPassive } set(v) { _dialogStatusSending = v } } - private var _dialogStatusSent: UIColor? - public var dialogStatusSent: UIColor { + fileprivate var _dialogStatusSent: UIColor? + open var dialogStatusSent: UIColor { get { return _dialogStatusSent != nil ? _dialogStatusSent! : dialogStatusPassive } set(v) { _dialogStatusSent = v } } - private var _dialogStatusReceived: UIColor? - public var dialogStatusReceived: UIColor { + fileprivate var _dialogStatusReceived: UIColor? + open var dialogStatusReceived: UIColor { get { return _dialogStatusReceived != nil ? _dialogStatusReceived! : dialogStatusPassive } set(v) { _dialogStatusReceived = v } } - private var _dialogStatusRead: UIColor? - public var dialogStatusRead: UIColor { + fileprivate var _dialogStatusRead: UIColor? + open var dialogStatusRead: UIColor { get { return _dialogStatusRead != nil ? _dialogStatusRead! : dialogStatusActive } set(v) { _dialogStatusRead = v } } - private var _dialogStatusError: UIColor? - public var dialogStatusError: UIColor { + fileprivate var _dialogStatusError: UIColor? + open var dialogStatusError: UIColor { get { return _dialogStatusError != nil ? _dialogStatusError! : dialogStatusDanger } set(v) { _dialogStatusError = v } } - public var dialogAvatarSize: CGFloat = 50 + open var dialogAvatarSize: CGFloat = 50 - private var _statusBackgroundIcon: UIImage? - public var statusBackgroundImage:UIImage { + fileprivate var _statusBackgroundIcon: UIImage? + open var statusBackgroundImage:UIImage { get { if (_statusBackgroundIcon == nil){ - let statusImage:UIImage = UIImage.bundled("bubble_service_bg")!.aa_imageWithColor(UIColor.blackColor().colorWithAlphaComponent(0.7)).imageWithRenderingMode(UIImageRenderingMode.AlwaysOriginal) + let statusImage:UIImage = UIImage.bundled("bubble_service_bg")!.aa_imageWithColor(UIColor.black.withAlphaComponent(0.7)).withRenderingMode(UIImageRenderingMode.alwaysOriginal) - let center:CGPoint = CGPointMake(statusImage.size.width / 2.0, statusImage.size.height / 2.0); + let center:CGPoint = CGPoint(x: statusImage.size.width / 2.0, y: statusImage.size.height / 2.0); let capInsets:UIEdgeInsets = UIEdgeInsetsMake(center.y, center.x, center.y, center.x); - _statusBackgroundIcon = statusImage.resizableImageWithCapInsets(capInsets, resizingMode: UIImageResizingMode.Stretch) + _statusBackgroundIcon = statusImage.resizableImage(withCapInsets: capInsets, resizingMode: UIImageResizingMode.stretch) return _statusBackgroundIcon! } else { return _statusBackgroundIcon! @@ -633,8 +633,8 @@ public class ActorStyle { // Contacts styles // - private var _contactTitleColor: UIColor? - public var contactTitleColor: UIColor { + fileprivate var _contactTitleColor: UIColor? + open var contactTitleColor: UIColor { get { return _contactTitleColor != nil ? _contactTitleColor! : vcTextColor } set(v) { _contactTitleColor = v } } @@ -643,26 +643,26 @@ public class ActorStyle { // Online styles // - private var _userOnlineColor: UIColor? - public var userOnlineColor: UIColor { + fileprivate var _userOnlineColor: UIColor? + open var userOnlineColor: UIColor { get { return _userOnlineColor != nil ? _userOnlineColor! : vcTintColor } set(v) { _userOnlineColor = v } } - private var _userOfflineColor: UIColor? - public var userOfflineColor: UIColor { + fileprivate var _userOfflineColor: UIColor? + open var userOfflineColor: UIColor { get { return _userOfflineColor != nil ? _userOfflineColor! : vcTextColor.alpha(0.54) } set(v) { _userOfflineColor = v } } - private var _userOnlineNavigationColor: UIColor? - public var userOnlineNavigationColor: UIColor { + fileprivate var _userOnlineNavigationColor: UIColor? + open var userOnlineNavigationColor: UIColor { get { return _userOnlineNavigationColor != nil ? _userOnlineNavigationColor! : userOnlineColor } set(v) { _userOnlineNavigationColor = v } } - private var _userOfflineNavigationColor: UIColor? - public var userOfflineNavigationColor: UIColor { + fileprivate var _userOfflineNavigationColor: UIColor? + open var userOfflineNavigationColor: UIColor { get { return _userOfflineNavigationColor != nil ? _userOfflineNavigationColor! : navigationSubtitleColor } set(v) { _userOfflineNavigationColor = v } } @@ -671,20 +671,20 @@ public class ActorStyle { // Compose styles // - private var _composeAvatarBgColor: UIColor? - public var composeAvatarBgColor: UIColor { + fileprivate var _composeAvatarBgColor: UIColor? + open var composeAvatarBgColor: UIColor { get { return _composeAvatarBgColor != nil ? _composeAvatarBgColor! : vcBgColor } set(v) { _composeAvatarBgColor = v } } - private var _composeAvatarBorderColor: UIColor? - public var composeAvatarBorderColor: UIColor { + fileprivate var _composeAvatarBorderColor: UIColor? + open var composeAvatarBorderColor: UIColor { get { return _composeAvatarBorderColor != nil ? _composeAvatarBorderColor! : vcSeparatorColor } set(v) { _composeAvatarBorderColor = v } } - private var _composeAvatarTextColor: UIColor? - public var composeAvatarTextColor: UIColor { + fileprivate var _composeAvatarTextColor: UIColor? + open var composeAvatarTextColor: UIColor { get { return _composeAvatarTextColor != nil ? _composeAvatarTextColor! : vcHintColor } set(v) { _composeAvatarTextColor = v } } @@ -695,18 +695,18 @@ public class ActorStyle { // /// Is Status Bar connecting status hidden - public var statusBarConnectingHidden = false + open var statusBarConnectingHidden = false /// Is Status Bar background color - private var _statusBarConnectingBgColor : UIColor? - public var statusBarConnectingBgColor: UIColor { + fileprivate var _statusBarConnectingBgColor : UIColor? + open var statusBarConnectingBgColor: UIColor { get { return _statusBarConnectingBgColor != nil ? _statusBarConnectingBgColor! : navigationBgColor } set(v) { _statusBarConnectingBgColor = v } } /// Is Status Bar background color - private var _statusBarConnectingTextColor : UIColor? - public var statusBarConnectingTextColor: UIColor { + fileprivate var _statusBarConnectingTextColor : UIColor? + open var statusBarConnectingTextColor: UIColor { get { return _statusBarConnectingTextColor != nil ? _statusBarConnectingTextColor! : navigationTitleColor } set(v) { _statusBarConnectingTextColor = v } } @@ -716,54 +716,54 @@ public class ActorStyle { // /// Welcome Page Background color - public var welcomeBgColor = UIColor(red: 94, green: 142, blue: 192) + open var welcomeBgColor = UIColor(red: 94, green: 142, blue: 192) /// Welcome Page Background image - public var welcomeBgImage: UIImage? = nil + open var welcomeBgImage: UIImage? = nil /// Welcome Page Title Color - public var welcomeTitleColor = UIColor.whiteColor() + open var welcomeTitleColor = UIColor.white /// Welcome Page Tagline Color - public var welcomeTaglineColor = UIColor.whiteColor() + open var welcomeTaglineColor = UIColor.white /// Welcome Page Signup Background Color - public var welcomeSignupBgColor = UIColor.whiteColor() + open var welcomeSignupBgColor = UIColor.white /// Welcome Page Signup Text Color - public var welcomeSignupTextColor = UIColor(red: 94, green: 142, blue: 192) + open var welcomeSignupTextColor = UIColor(red: 94, green: 142, blue: 192) /// Welcome Page Login Text Color - public var welcomeLoginTextColor = UIColor.whiteColor() + open var welcomeLoginTextColor = UIColor.white /// Welcome Logo - public var welcomeLogo: UIImage? = UIImage.bundled("logo_welcome") - public var welcomeLogoSize: CGSize = CGSize(width: 90, height: 90) - public var logoViewVerticalGap: CGFloat = 145 + open var welcomeLogo: UIImage? = UIImage.bundled("logo_welcome") + open var welcomeLogoSize: CGSize = CGSize(width: 90, height: 90) + open var logoViewVerticalGap: CGFloat = 145 // // Auth Screen // - public var authTintColor = UIColor(rgb: 0x007aff) + open var authTintColor = UIColor(rgb: 0x007aff) - public var authTitleColor = UIColor.blackColor().alpha(0.87) + open var authTitleColor = UIColor.black.alpha(0.87) - public var authHintColor = UIColor.alphaBlack(0.64) + open var authHintColor = UIColor.alphaBlack(0.64) - public var authTextColor = UIColor.alphaBlack(0.87) + open var authTextColor = UIColor.alphaBlack(0.87) - public var authSeparatorColor = UIColor.blackColor().alpha(0.2) + open var authSeparatorColor = UIColor.black.alpha(0.2) // // Settings VC // - public var vcSettingsContactsHeaderTextColor: UIColor { + open var vcSettingsContactsHeaderTextColor: UIColor { get { return _vcSettingsContactsHeaderTextColor != nil ? _vcSettingsContactsHeaderTextColor! : vcTextColor } set(v) { _vcSettingsContactsHeaderTextColor = v } } - private var _vcSettingsContactsHeaderTextColor : UIColor? + fileprivate var _vcSettingsContactsHeaderTextColor : UIColor? } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Auth/AAAuthEmailViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Auth/AAAuthEmailViewController.swift index 12e1a586e8..054772ccf5 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Auth/AAAuthEmailViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Auth/AAAuthEmailViewController.swift @@ -4,7 +4,7 @@ import Foundation -public class AAAuthEmailViewController: AAAuthViewController { +open class AAAuthEmailViewController: AAAuthViewController { let name: String @@ -28,12 +28,12 @@ public class AAAuthEmailViewController: AAAuthViewController { fatalError("init(coder:) has not been implemented") } - public override func viewDidLoad() { + open override func viewDidLoad() { - view.backgroundColor = UIColor.whiteColor() + view.backgroundColor = UIColor.white - scrollView.keyboardDismissMode = .OnDrag - scrollView.scrollEnabled = true + scrollView.keyboardDismissMode = .onDrag + scrollView.isScrollEnabled = true scrollView.alwaysBounceVertical = true welcomeLabel.font = UIFont.lightSystemFontOfSize(23) @@ -42,20 +42,20 @@ public class AAAuthEmailViewController: AAAuthViewController { welcomeLabel.numberOfLines = 1 welcomeLabel.minimumScaleFactor = 0.3 welcomeLabel.adjustsFontSizeToFitWidth = true - welcomeLabel.textAlignment = .Center + welcomeLabel.textAlignment = .center - hintLabel.font = UIFont.systemFontOfSize(14) + hintLabel.font = UIFont.systemFont(ofSize: 14) hintLabel.textColor = ActorSDK.sharedActor().style.authHintColor hintLabel.text = AALocalized("AuthEmailHint") hintLabel.numberOfLines = 1 - hintLabel.textAlignment = .Center + hintLabel.textAlignment = .center - emailField.font = UIFont.systemFontOfSize(17) + emailField.font = UIFont.systemFont(ofSize: 17) emailField.textColor = ActorSDK.sharedActor().style.authTextColor emailField.placeholder = AALocalized("AuthEmailPlaceholder") - emailField.keyboardType = .EmailAddress - emailField.autocapitalizationType = .None - emailField.autocorrectionType = .No + emailField.keyboardType = .emailAddress + emailField.autocapitalizationType = .none + emailField.autocorrectionType = .no emailFieldLine.backgroundColor = ActorSDK.sharedActor().style.authSeparatorColor @@ -84,7 +84,7 @@ public class AAAuthEmailViewController: AAAuthViewController { // Terms Of Service // if showTos { - let tosRange = NSRange(location: hintText.indexOf(tosText)!, length: tosText.length) + let tosRange = NSRange(location: hintText.indexOf(tosText!)!, length: tosText!.length) let tosLink = YYTextHighlight() tosLink.setColor(ActorSDK.sharedActor().style.authTintColor.alpha(0.56)) tosLink.tapAction = { (container, text, range, rect) in @@ -99,7 +99,7 @@ public class AAAuthEmailViewController: AAAuthViewController { // Privacy Policy // if showPrivacy { - let privacyRange = NSRange(location: hintText.indexOf(privacyText)!, length: privacyText.length) + let privacyRange = NSRange(location: hintText.indexOf(privacyText!)!, length: privacyText!.length) let privacyLink = YYTextHighlight() privacyLink.setColor(ActorSDK.sharedActor().style.authTintColor.alpha(0.56)) privacyLink.tapAction = { (container, text, range, rect) in @@ -110,21 +110,21 @@ public class AAAuthEmailViewController: AAAuthViewController { } termsLabel.attributedText = attributedTerms - termsLabel.font = UIFont.systemFontOfSize(14) + termsLabel.font = UIFont.systemFont(ofSize: 14) termsLabel.numberOfLines = 2 - termsLabel.textAlignment = .Center + termsLabel.textAlignment = .center } else { - termsLabel.hidden = true + termsLabel.isHidden = true } - if ActorSDK.sharedActor().authStrategy == .PhoneOnly || ActorSDK.sharedActor().authStrategy == .PhoneEmail { - usePhoneButton.setTitle(AALocalized("AuthEmailUsePhone"), forState: .Normal) - usePhoneButton.titleLabel?.font = UIFont.systemFontOfSize(14) - usePhoneButton.setTitleColor(ActorSDK.sharedActor().style.authTintColor, forState: .Normal) - usePhoneButton.setTitleColor(ActorSDK.sharedActor().style.authTintColor.alpha(0.56), forState: .Highlighted) - usePhoneButton.addTarget(self, action: #selector(AAAuthEmailViewController.usePhoneDidPressed), forControlEvents: .TouchUpInside) + if ActorSDK.sharedActor().authStrategy == .phoneOnly || ActorSDK.sharedActor().authStrategy == .phoneEmail { + usePhoneButton.setTitle(AALocalized("AuthEmailUsePhone"), for: UIControlState()) + usePhoneButton.titleLabel?.font = UIFont.systemFont(ofSize: 14) + usePhoneButton.setTitleColor(ActorSDK.sharedActor().style.authTintColor, for: UIControlState()) + usePhoneButton.setTitleColor(ActorSDK.sharedActor().style.authTintColor.alpha(0.56), for: .highlighted) + usePhoneButton.addTarget(self, action: #selector(AAAuthEmailViewController.usePhoneDidPressed), for: .touchUpInside) } else { - usePhoneButton.hidden = true + usePhoneButton.isHidden = true } scrollView.addSubview(welcomeLabel) @@ -138,30 +138,30 @@ public class AAAuthEmailViewController: AAAuthViewController { super.viewDidLoad() } - public override func viewDidLayoutSubviews() { + open override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() - welcomeLabel.frame = CGRectMake(20, 90 - 66, view.width - 40, 28) - hintLabel.frame = CGRectMake(20, 127 - 66, view.width - 40, 18) + welcomeLabel.frame = CGRect(x: 20, y: 90 - 66, width: view.width - 40, height: 28) + hintLabel.frame = CGRect(x: 20, y: 127 - 66, width: view.width - 40, height: 18) - emailField.frame = CGRectMake(20, 184 - 66, view.width - 40, 44) - emailFieldLine.frame = CGRectMake(10, 228 - 66, view.width - 20, 0.5) + emailField.frame = CGRect(x: 20, y: 184 - 66, width: view.width - 40, height: 44) + emailFieldLine.frame = CGRect(x: 10, y: 228 - 66, width: view.width - 20, height: 0.5) - termsLabel.frame = CGRectMake(20, 314 - 66, view.width - 40, 55) + termsLabel.frame = CGRect(x: 20, y: 314 - 66, width: view.width - 40, height: 55) - usePhoneButton.frame = CGRectMake(20, 375 - 66, view.width - 40, 38) + usePhoneButton.frame = CGRect(x: 20, y: 375 - 66, width: view.width - 40, height: 38) scrollView.frame = view.bounds - scrollView.contentSize = CGSizeMake(view.width, 420) + scrollView.contentSize = CGSize(width: view.width, height: 420) } - public func usePhoneDidPressed() { + open func usePhoneDidPressed() { let controllers = self.navigationController!.viewControllers let updatedControllers = Array(controllers[0..<(controllers.count - 1)]) + [AAAuthPhoneViewController(name: name)] self.navigationController?.setViewControllers(updatedControllers, animated: false) } - public override func nextDidTap() { + open override func nextDidTap() { let email = emailField.text! if !AATools.isValidEmail(email) { @@ -170,7 +170,7 @@ public class AAAuthEmailViewController: AAAuthViewController { return } - Actor.doStartAuthWithEmail(email).startUserAction().then { (res: ACAuthStartRes!) -> () in + Actor.doStartAuth(withEmail: email).startUserAction().then { (res: ACAuthStartRes!) -> () in if res.authMode.toNSEnum() == .OTP { self.navigateNext(AAAuthOTPViewController(email: email, name: self.name, transactionHash: res.transactionHash)) } else { @@ -179,8 +179,8 @@ public class AAAuthEmailViewController: AAAuthViewController { } } - public override func keyboardWillAppear(height: CGFloat) { - scrollView.frame = CGRectMake(0, 0, view.width, view.height - height) + open override func keyboardWillAppear(_ height: CGFloat) { + scrollView.frame = CGRect(x: 0, y: 0, width: view.width, height: view.height - height) if AADevice.isiPhone4 || AADevice.isiPhone5 { let height = scrollView.height - height @@ -191,13 +191,13 @@ public class AAAuthEmailViewController: AAAuthViewController { } } - public override func keyboardWillDisappear() { - scrollView.frame = CGRectMake(0, 0, view.width, view.height) + open override func keyboardWillDisappear() { + scrollView.frame = CGRect(x: 0, y: 0, width: view.width, height: view.height) } - public override func viewWillDisappear(animated: Bool) { + open override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) emailField.resignFirstResponder() } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Auth/AAAuthLogInViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Auth/AAAuthLogInViewController.swift index a4dd70465a..3188ec2c21 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Auth/AAAuthLogInViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Auth/AAAuthLogInViewController.swift @@ -4,7 +4,7 @@ import Foundation -public class AAAuthLogInViewController: AAAuthViewController { +open class AAAuthLogInViewController: AAAuthViewController { let scrollView = UIScrollView() @@ -17,41 +17,41 @@ public class AAAuthLogInViewController: AAAuthViewController { public override init() { super.init(nibName: nil, bundle: nil) - navigationItem.leftBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationCancel"), style: .Plain, target: self, action: #selector(AAViewController.dismiss)) +// navigationItem.leftBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationCancel"), style: .plain, target: self, action: #selector(AAViewController.dismiss)) } public required init(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - public override func viewDidLoad() { + open override func viewDidLoad() { - view.backgroundColor = UIColor.whiteColor() + view.backgroundColor = UIColor.white - scrollView.keyboardDismissMode = .OnDrag - scrollView.scrollEnabled = true + scrollView.keyboardDismissMode = .onDrag + scrollView.isScrollEnabled = true scrollView.alwaysBounceVertical = true welcomeLabel.font = UIFont.lightSystemFontOfSize(23) welcomeLabel.text = AALocalized("AuthLoginTitle").replace("{app_name}", dest: ActorSDK.sharedActor().appName) welcomeLabel.textColor = ActorSDK.sharedActor().style.authTitleColor - welcomeLabel.textAlignment = .Center + welcomeLabel.textAlignment = .center - if ActorSDK.sharedActor().authStrategy == .PhoneOnly { + if ActorSDK.sharedActor().authStrategy == .phoneOnly { field.placeholder = AALocalized("AuthLoginPhone") - field.keyboardType = .PhonePad - } else if ActorSDK.sharedActor().authStrategy == .EmailOnly { + field.keyboardType = .phonePad + } else if ActorSDK.sharedActor().authStrategy == .emailOnly { field.placeholder = AALocalized("AuthLoginEmail") - field.keyboardType = .EmailAddress - } else if ActorSDK.sharedActor().authStrategy == .PhoneEmail { + field.keyboardType = .emailAddress + } else if ActorSDK.sharedActor().authStrategy == .phoneEmail { field.placeholder = AALocalized("AuthLoginPhoneEmail") - field.keyboardType = .Default + field.keyboardType = .default } - field.autocapitalizationType = .None - field.autocorrectionType = .No + field.autocapitalizationType = .none + field.autocorrectionType = .no fieldLine.backgroundColor = ActorSDK.sharedActor().style.authSeparatorColor - fieldLine.opaque = false + fieldLine.isOpaque = false scrollView.addSubview(welcomeLabel) scrollView.addSubview(field) @@ -61,19 +61,19 @@ public class AAAuthLogInViewController: AAAuthViewController { super.viewDidLoad() } - public override func viewDidLayoutSubviews() { + open override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() - welcomeLabel.frame = CGRectMake(15, 90 - 66, view.width - 30, 28) + welcomeLabel.frame = CGRect(x: 15, y: 90 - 66, width: view.width - 30, height: 28) - fieldLine.frame = CGRectMake(10, 200 - 66, view.width - 20, 0.5) - field.frame = CGRectMake(20, 156 - 66, view.width - 40, 44) + fieldLine.frame = CGRect(x: 10, y: 200 - 66, width: view.width - 20, height: 0.5) + field.frame = CGRect(x: 20, y: 156 - 66, width: view.width - 40, height: 44) scrollView.frame = view.bounds - scrollView.contentSize = CGSizeMake(view.width, 240 - 66) + scrollView.contentSize = CGSize(width: view.width, height: 240 - 66) } - public override func nextDidTap() { + open override func nextDidTap() { let value = field.text!.trim() if value.length == 0 { shakeView(field, originalX: 20) @@ -81,9 +81,9 @@ public class AAAuthLogInViewController: AAAuthViewController { return } - if ActorSDK.sharedActor().authStrategy == .EmailOnly || ActorSDK.sharedActor().authStrategy == .PhoneEmail { + if ActorSDK.sharedActor().authStrategy == .emailOnly || ActorSDK.sharedActor().authStrategy == .phoneEmail { if (AATools.isValidEmail(value)) { - Actor.doStartAuthWithEmail(value).startUserAction().then { (res: ACAuthStartRes!) -> () in + Actor.doStartAuth(withEmail: value).startUserAction().then { (res: ACAuthStartRes!) -> () in if res.authMode.toNSEnum() == .OTP { self.navigateNext(AAAuthOTPViewController(email: value, transactionHash: res.transactionHash)) } else { @@ -94,13 +94,13 @@ public class AAAuthLogInViewController: AAAuthViewController { } } - if ActorSDK.sharedActor().authStrategy == .PhoneOnly || ActorSDK.sharedActor().authStrategy == .PhoneEmail { - let numbersSet = NSCharacterSet(charactersInString: "0123456789").invertedSet + if ActorSDK.sharedActor().authStrategy == .phoneOnly || ActorSDK.sharedActor().authStrategy == .phoneEmail { + let numbersSet = CharacterSet(charactersIn: "0123456789").inverted let stripped = value.strip(numbersSet) if let parsed = Int64(stripped) { - Actor.doStartAuthWithPhone(jlong(parsed)).startUserAction().then { (res: ACAuthStartRes!) -> () in + Actor.doStartAuth(withPhone: jlong(parsed)).startUserAction().then { (res: ACAuthStartRes!) -> () in if res.authMode.toNSEnum() == .OTP { - let formatted = RMPhoneFormat().format("\(parsed)") + let formatted = RMPhoneFormat().format("\(parsed)")! self.navigateNext(AAAuthOTPViewController(phone: formatted, transactionHash: res.transactionHash)) } else { self.alertUser(AALocalized("AuthUnsupported").replace("{app_name}", dest: ActorSDK.sharedActor().appName)) @@ -114,13 +114,13 @@ public class AAAuthLogInViewController: AAAuthViewController { shakeView(fieldLine, originalX: 10) } - public override func viewWillDisappear(animated: Bool) { + open override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) field.resignFirstResponder() } - public override func viewWillAppear(animated: Bool) { + open override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) if isFirstAppear { @@ -128,4 +128,4 @@ public class AAAuthLogInViewController: AAAuthViewController { field.becomeFirstResponder() } } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Auth/AAAuthNameViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Auth/AAAuthNameViewController.swift index 24e2e4608d..8eeec0803d 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Auth/AAAuthNameViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Auth/AAAuthNameViewController.swift @@ -4,7 +4,7 @@ import Foundation -public class AAAuthNameViewController: AAAuthViewController { +open class AAAuthNameViewController: AAAuthViewController { let transactionHash: String? @@ -20,34 +20,34 @@ public class AAAuthNameViewController: AAAuthViewController { self.transactionHash = transactionHash super.init(nibName: nil, bundle: nil) - navigationItem.leftBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationCancel"), style: .Plain, target: self, action: #selector(AAViewController.dismiss)) +// navigationItem.leftBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationCancel"), style: .plain, target: self, action: #selector(AAViewController.dismiss)) } public required init(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - public override func viewDidLoad() { + open override func viewDidLoad() { - view.backgroundColor = UIColor.whiteColor() + view.backgroundColor = UIColor.white - scrollView.keyboardDismissMode = .OnDrag - scrollView.scrollEnabled = true + scrollView.keyboardDismissMode = .onDrag + scrollView.isScrollEnabled = true scrollView.alwaysBounceVertical = true welcomeLabel.font = UIFont.lightSystemFontOfSize(23) welcomeLabel.text = AALocalized("AuthNameTitle") welcomeLabel.textColor = ActorSDK.sharedActor().style.authTitleColor - welcomeLabel.textAlignment = .Center + welcomeLabel.textAlignment = .center field.placeholder = AALocalized("AuthNamePlaceholder") - field.keyboardType = .Default - field.autocapitalizationType = .Words + field.keyboardType = .default + field.autocapitalizationType = .words field.textColor = ActorSDK.sharedActor().style.authTextColor - field.addTarget(self, action: #selector(AAAuthNameViewController.fieldDidChanged), forControlEvents: .EditingChanged) + field.addTarget(self, action: #selector(AAAuthNameViewController.fieldDidChanged), for: .editingChanged) fieldLine.backgroundColor = ActorSDK.sharedActor().style.authSeparatorColor - fieldLine.opaque = false + fieldLine.isOpaque = false scrollView.addSubview(welcomeLabel) scrollView.addSubview(fieldLine) @@ -58,15 +58,15 @@ public class AAAuthNameViewController: AAAuthViewController { super.viewDidLoad() } - public override func viewDidLayoutSubviews() { + open override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() - welcomeLabel.frame = CGRectMake(15, 90 - 66, view.width - 30, 28) - fieldLine.frame = CGRectMake(10, 200 - 66, view.width - 20, 0.5) - field.frame = CGRectMake(20, 156 - 66, view.width - 40, 44) + welcomeLabel.frame = CGRect(x: 15, y: 90 - 66, width: view.width - 30, height: 28) + fieldLine.frame = CGRect(x: 10, y: 200 - 66, width: view.width - 20, height: 0.5) + field.frame = CGRect(x: 20, y: 156 - 66, width: view.width - 40, height: 44) scrollView.frame = view.bounds - scrollView.contentSize = CGSizeMake(view.width, 240 - 66) + scrollView.contentSize = CGSize(width: view.width, height: 240 - 66) } func fieldDidChanged() { @@ -77,11 +77,11 @@ public class AAAuthNameViewController: AAAuthViewController { // } } - public override func nextDidTap() { + open override func nextDidTap() { let name = field.text!.trim() if name.length > 0 { if transactionHash != nil { - let promise = Actor.doSignupWithName(name, withSex: ACSex.UNKNOWN(), withTransaction: transactionHash!) + let promise = Actor.doSignup(withName: name, with: ACSex.unknown(), withTransaction: transactionHash!) promise.then { (r: ACAuthRes!) -> () in let promise = Actor.doCompleteAuth(r).startUserAction() promise.then { (r: JavaLangBoolean!) -> () in @@ -90,7 +90,7 @@ public class AAAuthNameViewController: AAAuthViewController { } promise.startUserAction() } else { - if ActorSDK.sharedActor().authStrategy == .PhoneOnly || ActorSDK.sharedActor().authStrategy == .PhoneEmail { + if ActorSDK.sharedActor().authStrategy == .phoneOnly || ActorSDK.sharedActor().authStrategy == .phoneEmail { navigateNext(AAAuthPhoneViewController(name: name)) } else { navigateNext(AAAuthEmailViewController(name: name)) @@ -102,7 +102,7 @@ public class AAAuthNameViewController: AAAuthViewController { } } - public override func viewWillAppear(animated: Bool) { + open override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) if isFirstAppear { @@ -111,9 +111,9 @@ public class AAAuthNameViewController: AAAuthViewController { } } - public override func viewWillDisappear(animated: Bool) { + open override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) field.resignFirstResponder() } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Auth/AAAuthNavigationController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Auth/AAAuthNavigationController.swift index 560d833e0b..4952944d74 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Auth/AAAuthNavigationController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Auth/AAAuthNavigationController.swift @@ -4,31 +4,31 @@ import Foundation -public class AAAuthNavigationController: UINavigationController { +open class AAAuthNavigationController: UINavigationController { - public override func viewDidLoad() { + open override func viewDidLoad() { super.viewDidLoad() navigationBar.setTransparentBackground() navigationBar.tintColor = ActorSDK.sharedActor().style.authTintColor navigationBar.hairlineHidden = true - view.backgroundColor = UIColor.whiteColor() + view.backgroundColor = UIColor.white } - public override func viewWillAppear(animated: Bool) { + open override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - UIApplication.sharedApplication().setStatusBarStyle(.Default, animated: true) + UIApplication.shared.setStatusBarStyle(.default, animated: true) } - public override func viewWillDisappear(animated: Bool) { + open override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) - UIApplication.sharedApplication().setStatusBarStyle(.LightContent, animated: true) + UIApplication.shared.setStatusBarStyle(.lightContent, animated: true) } - public override func preferredStatusBarStyle() -> UIStatusBarStyle { - return .Default + open override var preferredStatusBarStyle : UIStatusBarStyle { + return .default } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Auth/AAAuthOTPViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Auth/AAAuthOTPViewController.swift index 678eb40d5f..ae5022425b 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Auth/AAAuthOTPViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Auth/AAAuthOTPViewController.swift @@ -5,9 +5,9 @@ import Foundation import MessageUI -public class AAAuthOTPViewController: AAAuthViewController, MFMailComposeViewControllerDelegate { +open class AAAuthOTPViewController: AAAuthViewController, MFMailComposeViewControllerDelegate { - private static let DIAL_SECONDS: Int = 60 + fileprivate static let DIAL_SECONDS: Int = 60 let scrollView = UIScrollView() let welcomeLabel = UILabel() @@ -24,9 +24,9 @@ public class AAAuthOTPViewController: AAAuthViewController, MFMailComposeViewCon let email: String! let phone: String! - private var counterTimer: NSTimer! - private var dialed: Bool = false - private var counter = AAAuthOTPViewController.DIAL_SECONDS + fileprivate var counterTimer: Timer! + fileprivate var dialed: Bool = false + fileprivate var counter = AAAuthOTPViewController.DIAL_SECONDS public init(email: String, transactionHash: String) { self.transactionHash = transactionHash @@ -64,12 +64,12 @@ public class AAAuthOTPViewController: AAAuthViewController, MFMailComposeViewCon fatalError("init(coder:) has not been implemented") } - public override func viewDidLoad() { + open override func viewDidLoad() { - view.backgroundColor = UIColor.whiteColor() + view.backgroundColor = UIColor.white - scrollView.keyboardDismissMode = .OnDrag - scrollView.scrollEnabled = true + scrollView.keyboardDismissMode = .onDrag + scrollView.isScrollEnabled = true scrollView.alwaysBounceVertical = true welcomeLabel.font = UIFont.lightSystemFontOfSize(23) @@ -79,47 +79,47 @@ public class AAAuthOTPViewController: AAAuthViewController, MFMailComposeViewCon welcomeLabel.text = AALocalized("AuthOTPPhoneTitle") } welcomeLabel.textColor = ActorSDK.sharedActor().style.authTitleColor - welcomeLabel.textAlignment = .Center + welcomeLabel.textAlignment = .center - validateLabel.font = UIFont.systemFontOfSize(14) + validateLabel.font = UIFont.systemFont(ofSize: 14) if email != nil { validateLabel.text = email } else { validateLabel.text = phone } validateLabel.textColor = ActorSDK.sharedActor().style.authTintColor - validateLabel.textAlignment = .Center + validateLabel.textAlignment = .center - hintLabel.font = UIFont.systemFontOfSize(14) + hintLabel.font = UIFont.systemFont(ofSize: 14) if email != nil { hintLabel.text = AALocalized("AuthOTPEmailHint") } else { hintLabel.text = AALocalized("AuthOTPPhoneHint") } hintLabel.textColor = ActorSDK.sharedActor().style.authHintColor - hintLabel.textAlignment = .Center + hintLabel.textAlignment = .center hintLabel.numberOfLines = 2 - hintLabel.lineBreakMode = .ByWordWrapping + hintLabel.lineBreakMode = .byWordWrapping - codeField.font = UIFont.systemFontOfSize(17) + codeField.font = UIFont.systemFont(ofSize: 17) codeField.textColor = ActorSDK.sharedActor().style.authTextColor codeField.placeholder = AALocalized("AuthOTPPlaceholder") - codeField.keyboardType = .NumberPad - codeField.autocapitalizationType = .None - codeField.autocorrectionType = .No + codeField.keyboardType = .numberPad + codeField.autocapitalizationType = .none + codeField.autocorrectionType = .no codeFieldLine.backgroundColor = ActorSDK.sharedActor().style.authSeparatorColor if ActorSDK.sharedActor().supportEmail != nil { - haventReceivedCode.setTitle(AALocalized("AuthOTPNoCode"), forState: .Normal) + haventReceivedCode.setTitle(AALocalized("AuthOTPNoCode"), for: UIControlState()) } else { - haventReceivedCode.hidden = true + haventReceivedCode.isHidden = true } - haventReceivedCode.titleLabel?.font = UIFont.systemFontOfSize(14) - haventReceivedCode.setTitleColor(ActorSDK.sharedActor().style.authTintColor, forState: .Normal) - haventReceivedCode.setTitleColor(ActorSDK.sharedActor().style.authTintColor.alpha(0.64), forState: .Highlighted) - haventReceivedCode.setTitleColor(ActorSDK.sharedActor().style.authHintColor, forState: .Disabled) - haventReceivedCode.addTarget(self, action: #selector(AAAuthOTPViewController.haventReceivedCodeDidPressed), forControlEvents: .TouchUpInside) + haventReceivedCode.titleLabel?.font = UIFont.systemFont(ofSize: 14) + haventReceivedCode.setTitleColor(ActorSDK.sharedActor().style.authTintColor, for: UIControlState()) + haventReceivedCode.setTitleColor(ActorSDK.sharedActor().style.authTintColor.alpha(0.64), for: .highlighted) + haventReceivedCode.setTitleColor(ActorSDK.sharedActor().style.authHintColor, for: .disabled) + haventReceivedCode.addTarget(self, action: #selector(AAAuthOTPViewController.haventReceivedCodeDidPressed), for: .touchUpInside) scrollView.addSubview(welcomeLabel) scrollView.addSubview(validateLabel) @@ -132,20 +132,20 @@ public class AAAuthOTPViewController: AAAuthViewController, MFMailComposeViewCon super.viewDidLoad() } - public override func viewDidLayoutSubviews() { + open override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() - welcomeLabel.frame = CGRectMake(15, 90 - 66, view.width - 30, 28) - validateLabel.frame = CGRectMake(10, 127 - 66, view.width - 20, 17) - hintLabel.frame = CGRectMake(10, 154 - 66, view.width - 20, 56) + welcomeLabel.frame = CGRect(x: 15, y: 90 - 66, width: view.width - 30, height: 28) + validateLabel.frame = CGRect(x: 10, y: 127 - 66, width: view.width - 20, height: 17) + hintLabel.frame = CGRect(x: 10, y: 154 - 66, width: view.width - 20, height: 56) - codeField.frame = CGRectMake(20, 228 - 66, view.width - 40, 44) - codeFieldLine.frame = CGRectMake(10, 228 + 44 - 66, view.width - 20, 0.5) + codeField.frame = CGRect(x: 20, y: 228 - 66, width: view.width - 40, height: 44) + codeFieldLine.frame = CGRect(x: 10, y: 228 + 44 - 66, width: view.width - 20, height: 0.5) - haventReceivedCode.frame = CGRectMake(20, 297 - 66, view.width - 40, 56) + haventReceivedCode.frame = CGRect(x: 20, y: 297 - 66, width: view.width - 40, height: 56) scrollView.frame = view.bounds - scrollView.contentSize = CGSizeMake(view.width, 240 - 66) + scrollView.contentSize = CGSize(width: view.width, height: 240 - 66) } func haventReceivedCodeDidPressed() { @@ -168,11 +168,11 @@ public class AAAuthOTPViewController: AAAuthViewController, MFMailComposeViewCon } } - public func mailComposeController(controller: MFMailComposeViewController, didFinishWithResult result: MFMailComposeResult, error: NSError?) { - controller.dismissViewControllerAnimated(true, completion: nil) + open func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) { + controller.dismiss(animated: true, completion: nil) } - public override func nextDidTap() { + open override func nextDidTap() { let code = codeField.text!.trim() if code.length == 0 { @@ -188,7 +188,7 @@ public class AAAuthOTPViewController: AAAuthViewController, MFMailComposeViewCon if self.name == nil { self.navigateNext(AAAuthNameViewController(transactionHash: r.transactionHash)) } else { - let promise = Actor.doSignupWithName(self.name, withSex: ACSex.UNKNOWN(), withTransaction: r.transactionHash) + let promise = Actor.doSignup(withName: self.name, with: ACSex.unknown(), withTransaction: r.transactionHash) promise.then { (r: ACAuthRes!) -> () in Actor.doCompleteAuth(r).startUserAction().then { (r: JavaLangBoolean!) -> () in self.codeField.resignFirstResponder() @@ -218,12 +218,12 @@ public class AAAuthOTPViewController: AAAuthViewController, MFMailComposeViewCon } } - private func shakeField() { + fileprivate func shakeField() { shakeView(codeField, originalX: 20) shakeView(codeFieldLine, originalX: 10) } - public override func viewWillAppear(animated: Bool) { + open override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) if self.phone != nil { @@ -231,7 +231,7 @@ public class AAAuthOTPViewController: AAAuthViewController, MFMailComposeViewCon updateTimerText() if !dialed { - counterTimer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: #selector(AAAuthOTPViewController.updateTimer), userInfo: nil, repeats: true) + counterTimer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(AAAuthOTPViewController.updateTimer), userInfo: nil, repeats: true) } } } @@ -247,7 +247,7 @@ public class AAAuthOTPViewController: AAAuthViewController, MFMailComposeViewCon counterTimer = nil } - Actor.doSendCodeViaCall(self.transactionHash) + Actor.doSendCode(viaCall: self.transactionHash) } updateTimerText() @@ -257,11 +257,11 @@ public class AAAuthOTPViewController: AAAuthViewController, MFMailComposeViewCon func updateTimerText() { if dialed { if ActorSDK.sharedActor().supportEmail != nil { - haventReceivedCode.setTitle(AALocalized("AuthOTPNoCode"), forState: .Normal) - haventReceivedCode.hidden = false - haventReceivedCode.enabled = true + haventReceivedCode.setTitle(AALocalized("AuthOTPNoCode"), for: UIControlState()) + haventReceivedCode.isHidden = false + haventReceivedCode.isEnabled = true } else { - haventReceivedCode.hidden = true + haventReceivedCode.isHidden = true } } else { let min = counter / 60 @@ -272,13 +272,13 @@ public class AAAuthOTPViewController: AAAuthViewController, MFMailComposeViewCon let text = AALocalized("AuthOTPCallHint") .replace("{app_name}", dest: ActorSDK.sharedActor().appName) .replace("{time}", dest: time) - haventReceivedCode.setTitle(text, forState: .Normal) - haventReceivedCode.enabled = false - haventReceivedCode.hidden = false + haventReceivedCode.setTitle(text, for: UIControlState()) + haventReceivedCode.isEnabled = false + haventReceivedCode.isHidden = false } } - public override func viewWillDisappear(animated: Bool) { + open override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) if counterTimer != nil { diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Auth/AAAuthPhoneViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Auth/AAAuthPhoneViewController.swift index 81b5075515..94208bbd80 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Auth/AAAuthPhoneViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Auth/AAAuthPhoneViewController.swift @@ -36,10 +36,10 @@ class AAAuthPhoneViewController: AAAuthViewController, AACountryViewControllerDe override func viewDidLoad() { - view.backgroundColor = UIColor.whiteColor() + view.backgroundColor = UIColor.white - scrollView.keyboardDismissMode = .OnDrag - scrollView.scrollEnabled = true + scrollView.keyboardDismissMode = .onDrag + scrollView.isScrollEnabled = true scrollView.alwaysBounceVertical = true welcomeLabel.font = UIFont.lightSystemFontOfSize(23) @@ -48,31 +48,31 @@ class AAAuthPhoneViewController: AAAuthViewController, AACountryViewControllerDe welcomeLabel.numberOfLines = 1 welcomeLabel.minimumScaleFactor = 0.3 welcomeLabel.adjustsFontSizeToFitWidth = true - welcomeLabel.textAlignment = .Center + welcomeLabel.textAlignment = .center - hintLabel.font = UIFont.systemFontOfSize(14) + hintLabel.font = UIFont.systemFont(ofSize: 14) hintLabel.textColor = ActorSDK.sharedActor().style.authHintColor hintLabel.text = AALocalized("AuthPhoneHint") hintLabel.numberOfLines = 2 - hintLabel.textAlignment = .Center + hintLabel.textAlignment = .center - countryButton.setTitle(currentCountry.country, forState: .Normal) - countryButton.setTitleColor(ActorSDK.sharedActor().style.authTextColor, forState: .Normal) - countryButton.titleLabel!.font = UIFont.systemFontOfSize(17) + countryButton.setTitle(currentCountry.country, for: UIControlState()) + countryButton.setTitleColor(ActorSDK.sharedActor().style.authTextColor, for: UIControlState()) + countryButton.titleLabel!.font = UIFont.systemFont(ofSize: 17) countryButton.titleEdgeInsets = UIEdgeInsetsMake(11, 10, 11, 10) - countryButton.contentHorizontalAlignment = .Left - countryButton.setBackgroundImage(Imaging.imageWithColor(UIColor.alphaBlack(0.2), size: CGSizeMake(1, 1)), forState: .Highlighted) - countryButton.addTarget(self, action: #selector(AAAuthPhoneViewController.countryDidPressed), forControlEvents: .TouchUpInside) + countryButton.contentHorizontalAlignment = .left + countryButton.setBackgroundImage(Imaging.imageWithColor(UIColor.alphaBlack(0.2), size: CGSize(width: 1, height: 1)), for: .highlighted) + countryButton.addTarget(self, action: #selector(AAAuthPhoneViewController.countryDidPressed), for: .touchUpInside) countryButtonLine.backgroundColor = ActorSDK.sharedActor().style.authSeparatorColor - phoneCodeLabel.font = UIFont.systemFontOfSize(17) + phoneCodeLabel.font = UIFont.systemFont(ofSize: 17) phoneCodeLabel.textColor = ActorSDK.sharedActor().style.authHintColor phoneCodeLabel.text = "+\(currentCountry.code)" - phoneCodeLabel.textAlignment = .Center + phoneCodeLabel.textAlignment = .center phoneNumberLabel.currentIso = currentCountry.iso - phoneNumberLabel.keyboardType = .PhonePad + phoneNumberLabel.keyboardType = .phonePad phoneNumberLabel.placeholder = AALocalized("AuthPhonePlaceholder") phoneNumberLabel.textColor = ActorSDK.sharedActor().style.authTextColor @@ -102,13 +102,13 @@ class AAAuthPhoneViewController: AAAuthViewController, AACountryViewControllerDe // if showTos { let tosLink = YYTextHighlight() - let tosRange = NSRange(location: hintText.indexOf(tosText)!, length: tosText.length) + let tosRange = NSRange(location: hintText.indexOf(tosText!)!, length: tosText!.length) tosLink.setColor(ActorSDK.sharedActor().style.authTintColor.alpha(0.56)) tosLink.tapAction = { (container, text, range, rect) in if let url = ActorSDK.sharedActor().termsOfServiceUrl { self.openUrl(url) } else if let text = ActorSDK.sharedActor().termsOfServiceText { - self.presentViewController(AABigAlertController(alertTitle: tosText, alertMessage: text), animated: true, completion: nil) + self.present(AABigAlertController(alertTitle: tosText!, alertMessage: text), animated: true, completion: nil) } } attributedTerms.yy_setColor(ActorSDK.sharedActor().style.authTintColor, range: tosRange) @@ -121,13 +121,13 @@ class AAAuthPhoneViewController: AAAuthViewController, AACountryViewControllerDe // if showPrivacy { let privacyLink = YYTextHighlight() - let privacyRange = NSRange(location: hintText.indexOf(privacyText)!, length: privacyText.length) + let privacyRange = NSRange(location: hintText.indexOf(privacyText!)!, length: privacyText!.length) privacyLink.setColor(ActorSDK.sharedActor().style.authTintColor.alpha(0.56)) privacyLink.tapAction = { (container, text, range, rect) in if let url = ActorSDK.sharedActor().privacyPolicyUrl { self.openUrl(url) } else if let text = ActorSDK.sharedActor().privacyPolicyText { - self.presentViewController(AABigAlertController(alertTitle: privacyText, alertMessage: text), animated: true, completion: nil) + self.present(AABigAlertController(alertTitle: privacyText!, alertMessage: text), animated: true, completion: nil) } } attributedTerms.yy_setColor(ActorSDK.sharedActor().style.authTintColor, range: privacyRange) @@ -136,22 +136,22 @@ class AAAuthPhoneViewController: AAAuthViewController, AACountryViewControllerDe termsLabel.attributedText = attributedTerms - termsLabel.font = UIFont.systemFontOfSize(14) + termsLabel.font = UIFont.systemFont(ofSize: 14) termsLabel.numberOfLines = 2 - termsLabel.textAlignment = .Center + termsLabel.textAlignment = .center } else { - termsLabel.hidden = true + termsLabel.isHidden = true } - if ActorSDK.sharedActor().authStrategy == .EmailOnly || ActorSDK.sharedActor().authStrategy == .PhoneEmail { - useEmailButton.setTitle(AALocalized("AuthPhoneUseEmail"), forState: .Normal) - useEmailButton.titleLabel?.font = UIFont.systemFontOfSize(14) - useEmailButton.setTitleColor(ActorSDK.sharedActor().style.authTintColor, forState: .Normal) - useEmailButton.setTitleColor(ActorSDK.sharedActor().style.authTintColor.alpha(0.56), forState: .Highlighted) - useEmailButton.addTarget(self, action: #selector(AAAuthPhoneViewController.useEmailDidPressed), forControlEvents: .TouchUpInside) + if ActorSDK.sharedActor().authStrategy == .emailOnly || ActorSDK.sharedActor().authStrategy == .phoneEmail { + useEmailButton.setTitle(AALocalized("AuthPhoneUseEmail"), for: UIControlState()) + useEmailButton.titleLabel?.font = UIFont.systemFont(ofSize: 14) + useEmailButton.setTitleColor(ActorSDK.sharedActor().style.authTintColor, for: UIControlState()) + useEmailButton.setTitleColor(ActorSDK.sharedActor().style.authTintColor.alpha(0.56), for: .highlighted) + useEmailButton.addTarget(self, action: #selector(AAAuthPhoneViewController.useEmailDidPressed), for: .touchUpInside) } else { - useEmailButton.hidden = true + useEmailButton.isHidden = true } @@ -174,34 +174,34 @@ class AAAuthPhoneViewController: AAAuthViewController, AACountryViewControllerDe override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() - welcomeLabel.frame = CGRectMake(20, 90 - 66, view.width - 40, 28) - hintLabel.frame = CGRectMake(20, 127 - 66, view.width - 40, 34) + welcomeLabel.frame = CGRect(x: 20, y: 90 - 66, width: view.width - 40, height: 28) + hintLabel.frame = CGRect(x: 20, y: 127 - 66, width: view.width - 40, height: 34) - countryButton.frame = CGRectMake(10, 200 - 66, view.width - 20, 44) - countryButtonLine.frame = CGRectMake(10, 244 - 66, view.width - 20, 0.5) + countryButton.frame = CGRect(x: 10, y: 200 - 66, width: view.width - 20, height: 44) + countryButtonLine.frame = CGRect(x: 10, y: 244 - 66, width: view.width - 20, height: 0.5) - termsLabel.frame = CGRectMake(20, 314 - 66, view.width - 40, 55) + termsLabel.frame = CGRect(x: 20, y: 314 - 66, width: view.width - 40, height: 55) - useEmailButton.frame = CGRectMake(20, 375 - 66, view.width - 40, 38) + useEmailButton.frame = CGRect(x: 20, y: 375 - 66, width: view.width - 40, height: 38) resizePhoneLabels() scrollView.frame = view.bounds - scrollView.contentSize = CGSizeMake(view.width, 400) + scrollView.contentSize = CGSize(width: view.width, height: 400) } - private func resizePhoneLabels() { - phoneCodeLabel.frame = CGRectMake(10, 244 - 66, 80, 44) + fileprivate func resizePhoneLabels() { + phoneCodeLabel.frame = CGRect(x: 10, y: 244 - 66, width: 80, height: 44) phoneCodeLabel.sizeToFit() - phoneCodeLabel.frame = CGRectMake(10, 244 - 66, phoneCodeLabel.width + 32, 44) + phoneCodeLabel.frame = CGRect(x: 10, y: 244 - 66, width: phoneCodeLabel.width + 32, height: 44) - phoneNumberLabel.frame = CGRectMake(phoneCodeLabel.width + 10, 245 - 66, view.width - phoneCodeLabel.width, 44) - phoneCodeLabelLine.frame = CGRectMake(10, 288 - 66, view.width - 20, 0.5) + phoneNumberLabel.frame = CGRect(x: phoneCodeLabel.width + 10, y: 245 - 66, width: view.width - phoneCodeLabel.width, height: 44) + phoneCodeLabelLine.frame = CGRect(x: 10, y: 288 - 66, width: view.width - 20, height: 0.5) } - func countriesController(countriesController: AACountryViewController, didChangeCurrentIso currentIso: String) { + func countriesController(_ countriesController: AACountryViewController, didChangeCurrentIso currentIso: String) { currentCountry = AATelephony.getCountry(currentIso) - countryButton.setTitle(currentCountry.country, forState: .Normal) + countryButton.setTitle(currentCountry.country, for: UIControlState()) phoneCodeLabel.text = "+\(currentCountry.code)" phoneNumberLabel.currentIso = currentIso resizePhoneLabels() @@ -223,17 +223,17 @@ class AAAuthPhoneViewController: AAAuthViewController, AACountryViewControllerDe let numberStr = phoneNumberLabel.phoneNumber let number = phoneNumberLabel.phoneNumber.toJLong() - Actor.doStartAuthWithPhone(number).startUserAction().then { (res: ACAuthStartRes!) -> () in + Actor.doStartAuth(withPhone: number).startUserAction().then { (res: ACAuthStartRes!) -> () in if res.authMode.toNSEnum() == .OTP { - self.navigateNext(AAAuthOTPViewController(phone: numberStr, name: self.name, transactionHash: res.transactionHash)) + self.navigateNext(AAAuthOTPViewController(phone: numberStr!, name: self.name, transactionHash: res.transactionHash)) } else { self.alertUser(AALocalized("AuthUnsupported").replace("{app_name}", dest: ActorSDK.sharedActor().appName)) } } } - override func keyboardWillAppear(height: CGFloat) { - scrollView.frame = CGRectMake(0, 0, view.width, view.height - height) + override func keyboardWillAppear(_ height: CGFloat) { + scrollView.frame = CGRect(x: 0, y: 0, width: view.width, height: view.height - height) if AADevice.isiPhone4 || AADevice.isiPhone5 { @@ -246,13 +246,13 @@ class AAAuthPhoneViewController: AAAuthViewController, AACountryViewControllerDe } override func keyboardWillDisappear() { - scrollView.frame = CGRectMake(0, 0, view.width, view.height) + scrollView.frame = CGRect(x: 0, y: 0, width: view.width, height: view.height) } - override func viewWillDisappear(animated: Bool) { + override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) phoneNumberLabel.resignFirstResponder() } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Auth/AAAuthViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Auth/AAAuthViewController.swift index c19d222650..071cbba13d 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Auth/AAAuthViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Auth/AAAuthViewController.swift @@ -4,62 +4,62 @@ import Foundation -public class AAAuthViewController: AAViewController { +open class AAAuthViewController: AAViewController { - public let nextBarButton = UIButton() - private var keyboardHeight: CGFloat = 0 + open let nextBarButton = UIButton() + fileprivate var keyboardHeight: CGFloat = 0 - override public func viewDidLoad() { + override open func viewDidLoad() { super.viewDidLoad() - nextBarButton.setTitle(AALocalized("NavigationNext"), forState: .Normal) - nextBarButton.setTitleColor(UIColor.whiteColor(), forState: .Normal) - nextBarButton.setBackgroundImage(Imaging.roundedImage(UIColor(red: 94, green: 142, blue: 192), radius: 4), forState: .Normal) - nextBarButton.setBackgroundImage(Imaging.roundedImage(UIColor(red: 94, green: 142, blue: 192).alpha(0.7), radius: 4), forState: .Highlighted) - nextBarButton.addTarget(self, action: #selector(AAAuthViewController.nextDidTap), forControlEvents: .TouchUpInside) + nextBarButton.setTitle(AALocalized("NavigationNext"), for: UIControlState()) + nextBarButton.setTitleColor(UIColor.white, for: UIControlState()) + nextBarButton.setBackgroundImage(Imaging.roundedImage(UIColor(red: 94, green: 142, blue: 192), radius: 4), for: UIControlState()) + nextBarButton.setBackgroundImage(Imaging.roundedImage(UIColor(red: 94, green: 142, blue: 192).alpha(0.7), radius: 4), for: .highlighted) + nextBarButton.addTarget(self, action: #selector(AAAuthViewController.nextDidTap), for: .touchUpInside) view.addSubview(nextBarButton) } - override public func viewDidLayoutSubviews() { + override open func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() layoutNextBar() } - override public func viewWillAppear(animated: Bool) { + override open func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) // Forcing initial layout before keyboard show to avoid weird animations layoutNextBar() - NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(AAAuthViewController.keyboardWillAppearInt(_:)), name: UIKeyboardWillShowNotification, object: nil) - NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(AAAuthViewController.keyboardWillDisappearInt(_:)), name: UIKeyboardWillHideNotification, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(AAAuthViewController.keyboardWillAppearInt(_:)), name: NSNotification.Name.UIKeyboardWillShow, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(AAAuthViewController.keyboardWillDisappearInt(_:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil) } - private func layoutNextBar() { - nextBarButton.frame = CGRectMake(view.width - 95, view.height - 44 - keyboardHeight + 6, 85, 32) + fileprivate func layoutNextBar() { + nextBarButton.frame = CGRect(x: view.width - 95, y: view.height - 44 - keyboardHeight + 6, width: 85, height: 32) } - func keyboardWillAppearInt(notification: NSNotification) { - let dict = notification.userInfo! - let rect = dict[UIKeyboardFrameBeginUserInfoKey]!.CGRectValue + func keyboardWillAppearInt(_ notification: Notification) { + let dict = (notification as NSNotification).userInfo! + let rect = (dict[UIKeyboardFrameBeginUserInfoKey]! as AnyObject).cgRectValue - let orientation = UIApplication.sharedApplication().statusBarOrientation - let frameInWindow = self.view.superview!.convertRect(view.bounds, toView: nil) + let orientation = UIApplication.shared.statusBarOrientation + let frameInWindow = self.view.superview!.convert(view.bounds, to: nil) let windowBounds = view.window!.bounds - let keyboardTop: CGFloat = windowBounds.size.height - rect.height + let keyboardTop: CGFloat = windowBounds.size.height - rect!.height let heightCoveredByKeyboard: CGFloat if AADevice.isiPad { - if orientation == .LandscapeLeft || orientation == .LandscapeRight { + if orientation == .landscapeLeft || orientation == .landscapeRight { heightCoveredByKeyboard = frameInWindow.maxY - keyboardTop - 52 /*???*/ - } else if orientation == .Portrait || orientation == .PortraitUpsideDown { - heightCoveredByKeyboard = CGRectGetMaxY(frameInWindow) - keyboardTop + } else if orientation == .portrait || orientation == .portraitUpsideDown { + heightCoveredByKeyboard = frameInWindow.maxY - keyboardTop } else { heightCoveredByKeyboard = 0 } } else { - heightCoveredByKeyboard = rect.height + heightCoveredByKeyboard = (rect?.height)! } keyboardHeight = max(0, heightCoveredByKeyboard) @@ -68,34 +68,34 @@ public class AAAuthViewController: AAViewController { } - func keyboardWillDisappearInt(notification: NSNotification) { + func keyboardWillDisappearInt(_ notification: Notification) { keyboardHeight = 0 layoutNextBar() keyboardWillDisappear() } - public func keyboardWillAppear(height: CGFloat) { + open func keyboardWillAppear(_ height: CGFloat) { } - public func keyboardWillDisappear() { + open func keyboardWillDisappear() { } - override public func viewWillDisappear(animated: Bool) { + override open func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) - NSNotificationCenter.defaultCenter().removeObserver(self) + NotificationCenter.default.removeObserver(self) keyboardHeight = 0 layoutNextBar() } /// Call this method when authentication successful - public func onAuthenticated() { + open func onAuthenticated() { ActorSDK.sharedActor().didLoggedIn() } - public func nextDidTap() { + open func nextDidTap() { } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Auth/AAAuthWelcomeController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Auth/AAAuthWelcomeController.swift index 57ceefd074..2ff4c26b62 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Auth/AAAuthWelcomeController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Auth/AAAuthWelcomeController.swift @@ -4,7 +4,7 @@ import UIKit -public class AAWelcomeController: AAViewController { +open class AAWelcomeController: AAViewController { let bgImage: UIImageView = UIImageView() let logoView: UIImageView = UIImageView() @@ -23,44 +23,44 @@ public class AAWelcomeController: AAViewController { fatalError("init(coder:) has not been implemented") } - public override func loadView() { + open override func loadView() { super.loadView() self.view.backgroundColor = ActorSDK.sharedActor().style.welcomeBgColor self.bgImage.image = ActorSDK.sharedActor().style.welcomeBgImage - self.bgImage.hidden = ActorSDK.sharedActor().style.welcomeBgImage == nil - self.bgImage.contentMode = .ScaleAspectFill + self.bgImage.isHidden = ActorSDK.sharedActor().style.welcomeBgImage == nil + self.bgImage.contentMode = .scaleAspectFill self.logoView.image = ActorSDK.sharedActor().style.welcomeLogo self.size = ActorSDK.sharedActor().style.welcomeLogoSize self.logoViewVerticalGap = ActorSDK.sharedActor().style.logoViewVerticalGap appNameLabel.text = AALocalized("WelcomeTitle").replace("{app_name}", dest: ActorSDK.sharedActor().appName) - appNameLabel.textAlignment = .Center - appNameLabel.backgroundColor = UIColor.clearColor() + appNameLabel.textAlignment = .center + appNameLabel.backgroundColor = UIColor.clear appNameLabel.font = UIFont.mediumSystemFontOfSize(24) appNameLabel.textColor = ActorSDK.sharedActor().style.welcomeTitleColor someInfoLabel.text = AALocalized("WelcomeTagline") - someInfoLabel.textAlignment = .Center - someInfoLabel.backgroundColor = UIColor.clearColor() - someInfoLabel.font = UIFont.systemFontOfSize(16) + someInfoLabel.textAlignment = .center + someInfoLabel.backgroundColor = UIColor.clear + someInfoLabel.font = UIFont.systemFont(ofSize: 16) someInfoLabel.numberOfLines = 2 someInfoLabel.textColor = ActorSDK.sharedActor().style.welcomeTaglineColor - signupButton.setTitle(AALocalized("WelcomeSignUp"), forState: .Normal) + signupButton.setTitle(AALocalized("WelcomeSignUp"), for: UIControlState()) signupButton.titleLabel?.font = UIFont.mediumSystemFontOfSize(17) - signupButton.setTitleColor(ActorSDK.sharedActor().style.welcomeSignupTextColor, forState: .Normal) - signupButton.setBackgroundImage(Imaging.roundedImage(ActorSDK.sharedActor().style.welcomeSignupBgColor, radius: 22), forState: .Normal) - signupButton.setBackgroundImage(Imaging.roundedImage(ActorSDK.sharedActor().style.welcomeSignupBgColor.alpha(0.7), radius: 22), forState: .Highlighted) - signupButton.addTarget(self, action: #selector(AAWelcomeController.signupAction), forControlEvents: UIControlEvents.TouchUpInside) + signupButton.setTitleColor(ActorSDK.sharedActor().style.welcomeSignupTextColor, for: UIControlState()) + signupButton.setBackgroundImage(Imaging.roundedImage(ActorSDK.sharedActor().style.welcomeSignupBgColor, radius: 22), for: UIControlState()) + signupButton.setBackgroundImage(Imaging.roundedImage(ActorSDK.sharedActor().style.welcomeSignupBgColor.alpha(0.7), radius: 22), for: .highlighted) + signupButton.addTarget(self, action: #selector(AAWelcomeController.signupAction), for: UIControlEvents.touchUpInside) - signinButton.setTitle(AALocalized("WelcomeLogIn"), forState: .Normal) - signinButton.titleLabel?.font = UIFont.systemFontOfSize(17) - signinButton.setTitleColor(ActorSDK.sharedActor().style.welcomeLoginTextColor, forState: .Normal) - signinButton.setTitleColor(ActorSDK.sharedActor().style.welcomeLoginTextColor.alpha(0.7), forState: .Highlighted) - signinButton.addTarget(self, action: #selector(AAWelcomeController.signInAction), forControlEvents: UIControlEvents.TouchUpInside) + signinButton.setTitle(AALocalized("WelcomeLogIn"), for: UIControlState()) + signinButton.titleLabel?.font = UIFont.systemFont(ofSize: 17) + signinButton.setTitleColor(ActorSDK.sharedActor().style.welcomeLoginTextColor, for: UIControlState()) + signinButton.setTitleColor(ActorSDK.sharedActor().style.welcomeLoginTextColor.alpha(0.7), for: .highlighted) + signinButton.addTarget(self, action: #selector(AAWelcomeController.signInAction), for: UIControlEvents.touchUpInside) self.view.addSubview(self.bgImage) self.view.addSubview(self.logoView) @@ -70,45 +70,45 @@ public class AAWelcomeController: AAViewController { self.view.addSubview(self.signinButton) } - override public func viewDidLayoutSubviews() { + override open func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() if AADevice.isiPhone4 { - logoView.frame = CGRectMake((view.width - size.width) / 2, 90, size.width, size.height) - appNameLabel.frame = CGRectMake((view.width - 300) / 2, logoView.bottom + 30, 300, 29) - someInfoLabel.frame = CGRectMake((view.width - 300) / 2, appNameLabel.bottom + 8, 300, 56) + logoView.frame = CGRect(x: (view.width - size.width) / 2, y: 90, width: size.width, height: size.height) + appNameLabel.frame = CGRect(x: (view.width - 300) / 2, y: logoView.bottom + 30, width: 300, height: 29) + someInfoLabel.frame = CGRect(x: (view.width - 300) / 2, y: appNameLabel.bottom + 8, width: 300, height: 56) - signupButton.frame = CGRectMake((view.width - 136) / 2, view.height - 44 - 80, 136, 44) - signinButton.frame = CGRectMake((view.width - 136) / 2, view.height - 44 - 25, 136, 44) + signupButton.frame = CGRect(x: (view.width - 136) / 2, y: view.height - 44 - 80, width: 136, height: 44) + signinButton.frame = CGRect(x: (view.width - 136) / 2, y: view.height - 44 - 25, width: 136, height: 44) } else { - logoView.frame = CGRectMake((view.width - size.width) / 2, logoViewVerticalGap, size.width, size.height) - appNameLabel.frame = CGRectMake((view.width - 300) / 2, logoView.bottom + 35, 300, 29) - someInfoLabel.frame = CGRectMake((view.width - 300) / 2, appNameLabel.bottom + 8, 300, 56) + logoView.frame = CGRect(x: (view.width - size.width) / 2, y: logoViewVerticalGap, width: size.width, height: size.height) + appNameLabel.frame = CGRect(x: (view.width - 300) / 2, y: logoView.bottom + 35, width: 300, height: 29) + someInfoLabel.frame = CGRect(x: (view.width - 300) / 2, y: appNameLabel.bottom + 8, width: 300, height: 56) - signupButton.frame = CGRectMake((view.width - 136) / 2, view.height - 44 - 90, 136, 44) - signinButton.frame = CGRectMake((view.width - 136) / 2, view.height - 44 - 35, 136, 44) + signupButton.frame = CGRect(x: (view.width - 136) / 2, y: view.height - 44 - 90, width: 136, height: 44) + signinButton.frame = CGRect(x: (view.width - 136) / 2, y: view.height - 44 - 35, width: 136, height: 44) } self.bgImage.frame = view.bounds } - public func signupAction() { + open func signupAction() { // TODO: Remove BG after auth? - UIApplication.sharedApplication().keyWindow?.backgroundColor = ActorSDK.sharedActor().style.welcomeBgColor + UIApplication.shared.keyWindow?.backgroundColor = ActorSDK.sharedActor().style.welcomeBgColor self.presentElegantViewController(AAAuthNavigationController(rootViewController: AAAuthNameViewController())) } - public func signInAction() { + open func signInAction() { // TODO: Remove BG after auth? - UIApplication.sharedApplication().keyWindow?.backgroundColor = ActorSDK.sharedActor().style.welcomeBgColor + UIApplication.shared.keyWindow?.backgroundColor = ActorSDK.sharedActor().style.welcomeBgColor self.presentElegantViewController(AAAuthNavigationController(rootViewController: AAAuthLogInViewController())) } - public override func viewWillAppear(animated: Bool) { + open override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) // TODO: Fix after cancel? - UIApplication.sharedApplication().setStatusBarStyle(.LightContent, animated: true) + UIApplication.shared.setStatusBarStyle(.lightContent, animated: true) } } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Auth/AABigAlertController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Auth/AABigAlertController.swift index 308a62d809..8d20758d68 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Auth/AABigAlertController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Auth/AABigAlertController.swift @@ -6,21 +6,21 @@ import Foundation import UIKit -public class AABigAlertController: UIViewController,UIViewControllerTransitioningDelegate { +open class AABigAlertController: UIViewController,UIViewControllerTransitioningDelegate { - private let alertTitle: String - private let alertMessage: String + fileprivate let alertTitle: String + fileprivate let alertMessage: String - private var alertView : UIView! - private var alertTitleLabel : UILabel! - private var alertTextView : UITextView! - private var buttonOk : UIButton! + fileprivate var alertView : UIView! + fileprivate var alertTitleLabel : UILabel! + fileprivate var alertTextView : UITextView! + fileprivate var buttonOk : UIButton! public init(alertTitle: String, alertMessage: String) { self.alertTitle = alertTitle self.alertMessage = alertMessage super.init(nibName: nil, bundle: nil) - self.modalPresentationStyle = .Custom + self.modalPresentationStyle = .custom self.transitioningDelegate = self } @@ -28,66 +28,66 @@ public class AABigAlertController: UIViewController,UIViewControllerTransitionin fatalError("init(coder:) has not been implemented") } - public override func loadView() { + open override func loadView() { super.loadView() self.alertView = UIView() - self.alertView.frame = CGRectMake(self.view.frame.width/2 - 120, self.view.frame.height/2 - 165, 240, 330) - self.alertView.backgroundColor = UIColor.whiteColor() + self.alertView.frame = CGRect(x: self.view.frame.width/2 - 120, y: self.view.frame.height/2 - 165, width: 240, height: 330) + self.alertView.backgroundColor = UIColor.white self.alertView.layer.cornerRadius = 10 self.alertView.layer.masksToBounds = true self.view.addSubview(self.alertView) self.alertTitleLabel = UILabel() - self.alertTitleLabel.font = UIFont.boldSystemFontOfSize(17) - self.alertTitleLabel.frame = CGRectMake(10,10,220,30) + self.alertTitleLabel.font = UIFont.boldSystemFont(ofSize: 17) + self.alertTitleLabel.frame = CGRect(x: 10,y: 10,width: 220,height: 30) self.alertTitleLabel.text = alertTitle - self.alertTitleLabel.backgroundColor = UIColor.clearColor() - self.alertTitleLabel.textAlignment = .Center + self.alertTitleLabel.backgroundColor = UIColor.clear + self.alertTitleLabel.textAlignment = .center self.alertView.addSubview(self.alertTitleLabel) self.alertTextView = UITextView() self.alertTextView.font = UIFont.lightSystemFontOfSize(13) - self.alertTextView.backgroundColor = UIColor.clearColor() - self.alertTextView.editable = false + self.alertTextView.backgroundColor = UIColor.clear + self.alertTextView.isEditable = false self.alertTextView.text = alertMessage - self.alertTextView.frame = CGRectMake(10, 45, 220, 245); - self.alertTextView.userInteractionEnabled = true + self.alertTextView.frame = CGRect(x: 10, y: 45, width: 220, height: 245); + self.alertTextView.isUserInteractionEnabled = true self.alertView.addSubview(self.alertTextView) let separatorView = UIView() - separatorView.frame = CGRectMake(0, 290, 240, 0.5) - separatorView.backgroundColor = UIColor.lightGrayColor().colorWithAlphaComponent(0.5) + separatorView.frame = CGRect(x: 0, y: 290, width: 240, height: 0.5) + separatorView.backgroundColor = UIColor.lightGray.withAlphaComponent(0.5) self.alertView.addSubview(separatorView) - self.buttonOk = UIButton(type: UIButtonType.System) - self.buttonOk.setTitle(AALocalized("AlertOk"), forState: UIControlState.Normal) - self.buttonOk.setTitleColor(UIColor.blueColor(), forState: UIControlState.Normal) - self.buttonOk.frame = CGRectMake(0,291,240,39) - self.buttonOk.addTarget(self, action: #selector(AABigAlertController.closeController), forControlEvents: UIControlEvents.TouchUpInside) + self.buttonOk = UIButton(type: UIButtonType.system) + self.buttonOk.setTitle(AALocalized("AlertOk"), for: UIControlState()) + self.buttonOk.setTitleColor(UIColor.blue, for: UIControlState()) + self.buttonOk.frame = CGRect(x: 0,y: 291,width: 240,height: 39) + self.buttonOk.addTarget(self, action: #selector(AABigAlertController.closeController), for: UIControlEvents.touchUpInside) self.alertView.addSubview(self.buttonOk) } - public override func viewDidLoad() { + open override func viewDidLoad() { super.viewDidLoad() let touch = UITapGestureRecognizer(target: self, action: #selector(AABigAlertController.closeController)) self.view.addGestureRecognizer(touch) } - public func closeController() { - self.dismissViewControllerAnimated(true, completion: nil) + open func closeController() { + self.dismiss(animated: true, completion: nil) } - public func presentationControllerForPresentedViewController(presented: UIViewController, presentingViewController presenting: UIViewController, sourceViewController source: UIViewController) -> UIPresentationController? { + open func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? { if presented == self { - return AACustomPresentationController(presentedViewController: presented, presentingViewController: presenting) + return AACustomPresentationController(presentedViewController: presented, presenting: presenting) } return nil } - public func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? { + open func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { if presented == self { return AACustomPresentationAnimationController(isPresenting: true) @@ -97,7 +97,7 @@ public class AABigAlertController: UIViewController,UIViewControllerTransitionin } } - public func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { + open func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { if dismissed == self { return AACustomPresentationAnimationController(isPresenting: false) @@ -106,4 +106,4 @@ public class AABigAlertController: UIViewController,UIViewControllerTransitionin return nil } } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Auth/AACountryViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Auth/AACountryViewController.swift index 5a94430c14..e638416ecc 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Auth/AACountryViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Auth/AACountryViewController.swift @@ -5,85 +5,85 @@ import UIKit public protocol AACountryViewControllerDelegate { - func countriesController(countriesController: AACountryViewController, didChangeCurrentIso currentIso: String) + func countriesController(_ countriesController: AACountryViewController, didChangeCurrentIso currentIso: String) } -public class AACountryViewController: AATableViewController { +open class AACountryViewController: AATableViewController { - private var _countries: NSDictionary! - private var _letters: NSArray! + fileprivate var _countries: NSDictionary! + fileprivate var _letters: Array! - public var delegate: AACountryViewControllerDelegate? + open var delegate: AACountryViewControllerDelegate? public init() { - super.init(style: UITableViewStyle.Plain) + super.init(style: UITableViewStyle.plain) self.title = AALocalized("AuthCountryTitle") - let cancelButtonItem = UIBarButtonItem(title: AALocalized("NavigationCancel"), style: UIBarButtonItemStyle.Plain, target: self, action: Selector("dismiss")) - self.navigationItem.setLeftBarButtonItem(cancelButtonItem, animated: false) + let cancelButtonItem = UIBarButtonItem(title: AALocalized("NavigationCancel"), style: UIBarButtonItemStyle.plain, target: self, action: Selector("dismiss")) + self.navigationItem.setLeftBarButton(cancelButtonItem, animated: false) - self.content = ACAllEvents_Auth.AUTH_PICK_COUNTRY() + self.content = ACAllEvents_Auth.auth_PICK_COUNTRY() } public required init(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - public override func viewDidLoad() { + open override func viewDidLoad() { super.viewDidLoad() tableView.rowHeight = 44.0 - tableView.sectionIndexBackgroundColor = UIColor.clearColor() + tableView.sectionIndexBackgroundColor = UIColor.clear } - private func countries() -> NSDictionary { + fileprivate func countries() -> NSDictionary { if (_countries == nil) { let countries = NSMutableDictionary() - for (_, iso) in ABPhoneField.sortedIsoCodes().enumerate() { + for (_, iso) in ABPhoneField.sortedIsoCodes().enumerated() { let countryName = ABPhoneField.countryNameByCountryCode()[iso as! String] as! String let phoneCode = ABPhoneField.callingCodeByCountryCode()[iso as! String] as! String // if (self.searchBar.text.length == 0 || [countryName rangeOfString:self.searchBar.text options:NSCaseInsensitiveSearch].location != NSNotFound) - let countryLetter = countryName.substringToIndex(countryName.startIndex.advancedBy(1)) + let countryLetter = countryName.substring(to: countryName.characters.index(countryName.startIndex, offsetBy: 1)) if (countries[countryLetter] == nil) { countries[countryLetter] = NSMutableArray() } - countries[countryLetter]!.addObject([countryName, iso, phoneCode]) + (countries[countryLetter]! as AnyObject).add([countryName, iso, phoneCode]) } _countries = countries; } return _countries; } - private func letters() -> NSArray { - if (_letters == nil) { - _letters = (countries().allKeys as NSArray).sortedArrayUsingSelector(#selector(YYTextPosition.compare(_:))) - } - return _letters; + fileprivate func letters() -> Array { +// if (_letters == nil) { +// _letters = (countries().allKeys as Array).sortedArray(using: #selector(YYTextPosition.compare(_:))) +// } + return _letters } - public func sectionIndexTitlesForTableView(tableView: UITableView) -> [AnyObject]! { + open func sectionIndexTitlesForTableView(_ tableView: UITableView) -> [AnyObject]! { return letters() as [AnyObject] } - public func tableView(tableView: UITableView, sectionForSectionIndexTitle title: String, atIndex index: Int) -> Int { + open func tableView(_ tableView: UITableView, sectionForSectionIndexTitle title: String, atIndex index: Int) -> Int { return index } - public override func numberOfSectionsInTableView(tableView: UITableView) -> Int { + open override func numberOfSections(in tableView: UITableView) -> Int { return letters().count; } - public override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + open override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return (countries()[letters()[section] as! String] as! NSArray).count } - public override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { + open override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell: AAAuthCountryCell = tableView.dequeueCell(indexPath) - let letter = letters()[indexPath.section] as! String - let countryData = (countries().objectForKey(letter) as! NSArray)[indexPath.row] as! [String] + let letter = letters()[(indexPath as NSIndexPath).section] as! String + let countryData = (countries().object(forKey: letter) as! NSArray)[(indexPath as NSIndexPath).row] as! [String] cell.setTitle(countryData[0]) cell.setCode("+\(countryData[2])") @@ -92,28 +92,28 @@ public class AACountryViewController: AATableViewController { return cell } - public func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { - tableView.deselectRowAtIndexPath(indexPath, animated: true) + open func tableView(_ tableView: UITableView, didSelectRowAtIndexPath indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) - let letter = letters()[indexPath.section] as! String - let countryData = (countries().objectForKey(letter) as! NSArray)[indexPath.row] as! [String] + let letter = letters()[(indexPath as NSIndexPath).section] as! String + let countryData = (countries().object(forKey: letter) as! NSArray)[(indexPath as NSIndexPath).row] as! [String] delegate?.countriesController(self, didChangeCurrentIso: countryData[1]) dismiss() } - public func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { + open func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return 25.0 } - public func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + open func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { return letters()[section] as? String } - public override func viewWillDisappear(animated: Bool) { + open override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) - UIApplication.sharedApplication().setStatusBarStyle(.Default, animated: true) + UIApplication.shared.setStatusBarStyle(.default, animated: true) } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Auth/Cells/AAAuthCountryCell.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Auth/Cells/AAAuthCountryCell.swift index 21cd7091a9..0e5da9bffb 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Auth/Cells/AAAuthCountryCell.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Auth/Cells/AAAuthCountryCell.swift @@ -4,18 +4,18 @@ import UIKit -public class AAAuthCountryCell: UITableViewCell { +open class AAAuthCountryCell: UITableViewCell { // MARK: - // MARK: Private vars - private var titleLabel: UILabel! - private var codeLabel: UILabel! + fileprivate var titleLabel: UILabel! + fileprivate var codeLabel: UILabel! // MARK: - // MARK: Public vars - public var searchMode: Bool! + open var searchMode: Bool! // MARK: - // MARK: Constructors @@ -24,19 +24,19 @@ public class AAAuthCountryCell: UITableViewCell { super.init(style: style, reuseIdentifier: reuseIdentifier) titleLabel = UILabel() - titleLabel.autoresizingMask = UIViewAutoresizing.FlexibleWidth - titleLabel.font = UIFont.systemFontOfSize(17.0) - titleLabel.textColor = UIColor.blackColor() - titleLabel.backgroundColor = UIColor.whiteColor() + titleLabel.autoresizingMask = UIViewAutoresizing.flexibleWidth + titleLabel.font = UIFont.systemFont(ofSize: 17.0) + titleLabel.textColor = UIColor.black + titleLabel.backgroundColor = UIColor.white contentView.addSubview(titleLabel) codeLabel = UILabel() - codeLabel.font = UIFont.boldSystemFontOfSize(17) - codeLabel.backgroundColor = UIColor.whiteColor() - codeLabel.textColor = UIColor.blackColor() - codeLabel.autoresizingMask = UIViewAutoresizing.FlexibleLeftMargin - codeLabel.contentMode = UIViewContentMode.Right - codeLabel.textAlignment = NSTextAlignment.Right + codeLabel.font = UIFont.boldSystemFont(ofSize: 17) + codeLabel.backgroundColor = UIColor.white + codeLabel.textColor = UIColor.black + codeLabel.autoresizingMask = UIViewAutoresizing.flexibleLeftMargin + codeLabel.contentMode = UIViewContentMode.right + codeLabel.textAlignment = NSTextAlignment.right contentView.addSubview(codeLabel) } @@ -47,15 +47,15 @@ public class AAAuthCountryCell: UITableViewCell { // MARK: - // MARK: Setters - public func setTitle(title: String) { + open func setTitle(_ title: String) { titleLabel.text = title } - public func setCode(code: String) { + open func setCode(_ code: String) { codeLabel.text = code } - public func setSearchMode(searchMode: Bool) { + open func setSearchMode(_ searchMode: Bool) { self.searchMode = searchMode let codeLabelWidth: CGFloat = 50.0 diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Calls/AACallViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Calls/AACallViewController.swift index d843dba394..97225246fe 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Calls/AACallViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Calls/AACallViewController.swift @@ -5,14 +5,14 @@ import Foundation import AVFoundation -public class AACallViewController: AAViewController, RTCEAGLVideoViewDelegate { +open class AACallViewController: AAViewController, RTCEAGLVideoViewDelegate { - public let binder = AABinder() - public let callId: jlong - public let call: ACCallVM - public let senderAvatar: AAAvatarView = AAAvatarView() - public let peerTitle = UILabel() - public let callState = UILabel() + open let binder = AABinder() + open let callId: jlong + open let call: ACCallVM + open let senderAvatar: AAAvatarView = AAAvatarView() + open let peerTitle = UILabel() + open let callState = UILabel() var remoteView = RTCEAGLVideoView() var remoteVideoSize: CGSize! @@ -22,16 +22,16 @@ public class AACallViewController: AAViewController, RTCEAGLVideoViewDelegate { var localVideoTrack: RTCVideoTrack! var remoteVideoTrack: RTCVideoTrack! - public let answerCallButton = UIButton() - public let answerCallButtonText = UILabel() - public let declineCallButton = UIButton() - public let declineCallButtonText = UILabel() + open let answerCallButton = UIButton() + open let answerCallButtonText = UILabel() + open let declineCallButton = UIButton() + open let declineCallButtonText = UILabel() - public let muteButton = AACircleButton(size: 72) + open let muteButton = AACircleButton(size: 72) // public let videoButton = AACircleButton(size: 72) var isScheduledDispose = false - var timer: NSTimer? + var timer: Timer? public init(callId: jlong) { self.callId = callId @@ -44,7 +44,7 @@ public class AACallViewController: AAViewController, RTCEAGLVideoViewDelegate { fatalError("init(coder:) has not been implemented") } - public override func viewDidLoad() { + open override func viewDidLoad() { super.viewDidLoad() self.view.backgroundColor = UIColor(rgb: 0x2a4463) @@ -53,28 +53,28 @@ public class AACallViewController: AAViewController, RTCEAGLVideoViewDelegate { // Buttons // - answerCallButton.setImage(UIImage.bundled("ic_call_answer_44")!.tintImage(UIColor.whiteColor()), forState: .Normal) - answerCallButton.setBackgroundImage(Imaging.roundedImage(UIColor(red: 61/255.0, green: 217/255.0, blue: 90/255.0, alpha: 1.0), size: CGSizeMake(74, 74), radius: 37), forState: .Normal) + answerCallButton.setImage(UIImage.bundled("ic_call_answer_44")!.tintImage(UIColor.white), for: UIControlState()) + answerCallButton.setBackgroundImage(Imaging.roundedImage(UIColor(red: 61/255.0, green: 217/255.0, blue: 90/255.0, alpha: 1.0), size: CGSize(width: 74, height: 74), radius: 37), for: UIControlState()) answerCallButton.viewDidTap = { - Actor.answerCallWithCallId(self.callId) + Actor.answerCall(withCallId: self.callId) } answerCallButtonText.font = UIFont.thinSystemFontOfSize(16) - answerCallButtonText.textColor = UIColor.whiteColor() - answerCallButtonText.textAlignment = NSTextAlignment.Center + answerCallButtonText.textColor = UIColor.white + answerCallButtonText.textAlignment = NSTextAlignment.center answerCallButtonText.text = AALocalized("CallsAnswer") - answerCallButtonText.bounds = CGRectMake(0, 0, 72, 44) + answerCallButtonText.bounds = CGRect(x: 0, y: 0, width: 72, height: 44) answerCallButtonText.numberOfLines = 2 - declineCallButton.setImage(UIImage.bundled("ic_call_end_44")!.tintImage(UIColor.whiteColor()), forState: .Normal) - declineCallButton.setBackgroundImage(Imaging.roundedImage(UIColor(red: 217/255.0, green: 80/255.0, blue:61/255.0, alpha: 1.0), size: CGSizeMake(74, 74), radius: 37), forState: .Normal) + declineCallButton.setImage(UIImage.bundled("ic_call_end_44")!.tintImage(UIColor.white), for: UIControlState()) + declineCallButton.setBackgroundImage(Imaging.roundedImage(UIColor(red: 217/255.0, green: 80/255.0, blue:61/255.0, alpha: 1.0), size: CGSize(width: 74, height: 74), radius: 37), for: UIControlState()) declineCallButton.viewDidTap = { - Actor.endCallWithCallId(self.callId) + Actor.endCall(withCallId: self.callId) } declineCallButtonText.font = UIFont.thinSystemFontOfSize(16) - declineCallButtonText.textColor = UIColor.whiteColor() - declineCallButtonText.textAlignment = NSTextAlignment.Center + declineCallButtonText.textColor = UIColor.white + declineCallButtonText.textAlignment = NSTextAlignment.center declineCallButtonText.text = AALocalized("CallsDecline") - declineCallButtonText.bounds = CGRectMake(0, 0, 72, 44) + declineCallButtonText.bounds = CGRect(x: 0, y: 0, width: 72, height: 44) declineCallButtonText.numberOfLines = 2 @@ -86,7 +86,7 @@ public class AACallViewController: AAViewController, RTCEAGLVideoViewDelegate { muteButton.title = AALocalized("CallsMute") muteButton.alpha = 0 muteButton.button.viewDidTap = { - Actor.toggleCallMuteWithCallId(self.callId) + Actor.toggleCallMute(withCallId: self.callId) } // videoButton.image = UIImage.bundled("ic_video_44") @@ -102,33 +102,33 @@ public class AACallViewController: AAViewController, RTCEAGLVideoViewDelegate { // Video ViewPorts // - localView.backgroundColor = UIColor.whiteColor() + localView.backgroundColor = UIColor.white localView.alpha = 0 localView.layer.cornerRadius = 15 localView.layer.borderWidth = 1 - localView.layer.borderColor = UIColor.grayColor().CGColor + localView.layer.borderColor = UIColor.gray.cgColor localView.layer.shadowRadius = 1 localView.clipsToBounds = true - localView.contentMode = .ScaleAspectFit + localView.contentMode = .scaleAspectFit remoteView.alpha = 0 - remoteView.backgroundColor = UIColor.blackColor() + remoteView.backgroundColor = UIColor.black remoteView.delegate = self - remoteView.contentMode = .ScaleAspectFit + remoteView.contentMode = .scaleAspectFit // // Peer Info // - peerTitle.textColor = UIColor.whiteColor().alpha(0.87) - peerTitle.textAlignment = NSTextAlignment.Center + peerTitle.textColor = UIColor.white.alpha(0.87) + peerTitle.textAlignment = NSTextAlignment.center peerTitle.font = UIFont.thinSystemFontOfSize(42) peerTitle.minimumScaleFactor = 0.3 - callState.textColor = UIColor.whiteColor() - callState.textAlignment = NSTextAlignment.Center - callState.font = UIFont.systemFontOfSize(19) + callState.textColor = UIColor.white + callState.textAlignment = NSTextAlignment.center + callState.font = UIFont.systemFont(ofSize: 19) self.view.addSubview(senderAvatar) @@ -144,18 +144,18 @@ public class AACallViewController: AAViewController, RTCEAGLVideoViewDelegate { self.view.addSubview(localView) } - public override func viewWillLayoutSubviews() { + open override func viewWillLayoutSubviews() { super.viewWillLayoutSubviews() - senderAvatar.frame = CGRectMake((self.view.width - 104) / 2, 60, 108, 108) - peerTitle.frame = CGRectMake(22, senderAvatar.bottom + 22, view.width - 44, 42) - callState.frame = CGRectMake(0, peerTitle.bottom + 8, view.width, 22) + senderAvatar.frame = CGRect(x: (self.view.width - 104) / 2, y: 60, width: 108, height: 108) + peerTitle.frame = CGRect(x: 22, y: senderAvatar.bottom + 22, width: view.width - 44, height: 42) + callState.frame = CGRect(x: 0, y: peerTitle.bottom + 8, width: view.width, height: 22) layoutButtons() layoutVideo() } - public func videoView(videoView: RTCEAGLVideoView!, didChangeVideoSize size: CGSize) { + open func videoView(_ videoView: RTCEAGLVideoView!, didChangeVideoSize size: CGSize) { if videoView == remoteView { self.remoteVideoSize = size } else if videoView == localView { @@ -165,9 +165,9 @@ public class AACallViewController: AAViewController, RTCEAGLVideoViewDelegate { layoutVideo() } - private func layoutButtons() { + fileprivate func layoutButtons() { - muteButton.frame = CGRectMake((self.view.width / 3 - 84) / 2, self.view.height - 72 - 49, 84, 72 + 5 + 44) + muteButton.frame = CGRect(x: (self.view.width / 3 - 84) / 2, y: self.view.height - 72 - 49, width: 84, height: 72 + 5 + 44) // videoButton.frame = CGRectMake(2 * self.view.width / 3 + (self.view.width / 3 - 84) / 2, self.view.height - 72 - 49, 84, 72 + 5 + 44) // if call.isVideoPreferred.boolValue { // videoButton.hidden = true @@ -175,42 +175,42 @@ public class AACallViewController: AAViewController, RTCEAGLVideoViewDelegate { // videoButton.hidden = false // } - if !declineCallButton.hidden || !answerCallButton.hidden { - if !declineCallButton.hidden && !answerCallButton.hidden { - declineCallButton.frame = CGRectMake(25, self.view.height - 72 - 49, 72, 72) + if !declineCallButton.isHidden || !answerCallButton.isHidden { + if !declineCallButton.isHidden && !answerCallButton.isHidden { + declineCallButton.frame = CGRect(x: 25, y: self.view.height - 72 - 49, width: 72, height: 72) declineCallButtonText.under(declineCallButton.frame, offset: 5) - answerCallButton.frame = CGRectMake(self.view.width - 72 - 25, self.view.height - 72 - 49, 72, 72) + answerCallButton.frame = CGRect(x: self.view.width - 72 - 25, y: self.view.height - 72 - 49, width: 72, height: 72) answerCallButtonText.under(answerCallButton.frame, offset: 5) } else { - if !answerCallButton.hidden { - answerCallButton.frame = CGRectMake((self.view.width - 72) / 2, self.view.height - 72 - 49, 72, 72) + if !answerCallButton.isHidden { + answerCallButton.frame = CGRect(x: (self.view.width - 72) / 2, y: self.view.height - 72 - 49, width: 72, height: 72) answerCallButtonText.under(answerCallButton.frame, offset: 5) } - if !declineCallButton.hidden { + if !declineCallButton.isHidden { // declineCallButton.frame = CGRectMake((self.view.width - 72) / 2, self.view.height - 72 - 49, 72, 72) - declineCallButton.frame = CGRectMake(self.view.width - 72 - 25, self.view.height - 72 - 49, 72, 72) + declineCallButton.frame = CGRect(x: self.view.width - 72 - 25, y: self.view.height - 72 - 49, width: 72, height: 72) declineCallButtonText.under(declineCallButton.frame, offset: 5) } } } } - private func layoutVideo() { + fileprivate func layoutVideo() { if self.remoteVideoSize == nil { - remoteView.frame = CGRectMake(0, 0, self.view.width, self.view.height) + remoteView.frame = CGRect(x: 0, y: 0, width: self.view.width, height: self.view.height) } else { - remoteView.frame = AVMakeRectWithAspectRatioInsideRect(remoteVideoSize, view.bounds) + remoteView.frame = AVMakeRect(aspectRatio: remoteVideoSize, insideRect: view.bounds) } if self.localVideoSize == nil { - localView.frame = CGRectMake(self.view.width - 100 - 10, 10, 100, 120) + localView.frame = CGRect(x: self.view.width - 100 - 10, y: 10, width: 100, height: 120) } else { - let rect = AVMakeRectWithAspectRatioInsideRect(localVideoSize, CGRectMake(0, 0, 120, 120)) - localView.frame = CGRectMake(self.view.width - rect.width - 10, 10, rect.width, rect.height) + let rect = AVMakeRect(aspectRatio: localVideoSize, insideRect: CGRect(x: 0, y: 0, width: 120, height: 120)) + localView.frame = CGRect(x: self.view.width - rect.width - 10, y: 10, width: rect.width, height: rect.height) } } - private func CGSizeAspectFit(aspectRatio:CGSize, boundingSize:CGSize) -> CGSize { + fileprivate func CGSizeAspectFit(_ aspectRatio:CGSize, boundingSize:CGSize) -> CGSize { var aspectFitSize = boundingSize let mW = boundingSize.width / aspectRatio.width let mH = boundingSize.height / aspectRatio.height @@ -225,7 +225,7 @@ public class AACallViewController: AAViewController, RTCEAGLVideoViewDelegate { return aspectFitSize } - private func CGSizeAspectFill(aspectRatio:CGSize, minimumSize:CGSize) -> CGSize { + fileprivate func CGSizeAspectFill(_ aspectRatio:CGSize, minimumSize:CGSize) -> CGSize { var aspectFillSize = minimumSize let mW = minimumSize.width / aspectRatio.width let mH = minimumSize.height / aspectRatio.height @@ -240,83 +240,83 @@ public class AACallViewController: AAViewController, RTCEAGLVideoViewDelegate { return aspectFillSize } - public override func viewWillAppear(animated: Bool) { + open override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) // UI Configuration - UIApplication.sharedApplication().setStatusBarStyle(UIStatusBarStyle.LightContent, animated: true) + UIApplication.shared.setStatusBarStyle(UIStatusBarStyle.lightContent, animated: true) - UIDevice.currentDevice().proximityMonitoringEnabled = true + UIDevice.current.isProximityMonitoringEnabled = true // // Binding State // - binder.bind(call.isAudioEnabled) { (value: JavaLangBoolean!) -> () in - self.muteButton.filled = !value.booleanValue() + binder.bind(call.isAudioEnabled) { (value: JavaLangBoolean?) -> () in + self.muteButton.filled = !value!.booleanValue() } - binder.bind(call.state) { (value: ACCallState!) -> () in - if (ACCallState_Enum.RINGING == value.toNSEnum()) { + binder.bind(call.state) { (value: ACCallState?) -> () in + if (ACCallState_Enum.RINGING == value!.toNSEnum()) { if (self.call.isOutgoing) { self.muteButton.showViewAnimated() // self.videoButton.showViewAnimated() - self.answerCallButton.hidden = true - self.answerCallButtonText.hidden = true - self.declineCallButton.hidden = false - self.declineCallButtonText.hidden = true + self.answerCallButton.isHidden = true + self.answerCallButtonText.isHidden = true + self.declineCallButton.isHidden = false + self.declineCallButtonText.isHidden = true self.callState.text = AALocalized("CallStateRinging") } else { - self.answerCallButton.hidden = false - self.answerCallButtonText.hidden = false - self.declineCallButton.hidden = false - self.declineCallButtonText.hidden = false + self.answerCallButton.isHidden = false + self.answerCallButtonText.isHidden = false + self.declineCallButton.isHidden = false + self.declineCallButtonText.isHidden = false self.callState.text = AALocalized("CallStateIncoming") } self.layoutButtons() - } else if (ACCallState_Enum.CONNECTING == value.toNSEnum()) { + } else if (ACCallState_Enum.CONNECTING == value!.toNSEnum()) { self.muteButton.showViewAnimated() // self.videoButton.showViewAnimated() - self.answerCallButton.hidden = true - self.answerCallButtonText.hidden = true - self.declineCallButton.hidden = false - self.declineCallButtonText.hidden = true + self.answerCallButton.isHidden = true + self.answerCallButtonText.isHidden = true + self.declineCallButton.isHidden = false + self.declineCallButtonText.isHidden = true self.callState.text = AALocalized("CallStateConnecting") self.layoutButtons() - } else if (ACCallState_Enum.IN_PROGRESS == value.toNSEnum()) { + } else if (ACCallState_Enum.IN_PROGRESS == value!.toNSEnum()) { self.muteButton.showViewAnimated() // self.videoButton.showViewAnimated() - self.answerCallButton.hidden = true - self.answerCallButtonText.hidden = true - self.declineCallButton.hidden = false - self.declineCallButtonText.hidden = true + self.answerCallButton.isHidden = true + self.answerCallButtonText.isHidden = true + self.declineCallButton.isHidden = false + self.declineCallButtonText.isHidden = true self.startTimer() self.layoutButtons() - } else if (ACCallState_Enum.ENDED == value.toNSEnum()) { + } else if (ACCallState_Enum.ENDED == value!.toNSEnum()) { self.muteButton.hideViewAnimated() // self.videoButton.hideViewAnimated() - self.answerCallButton.hidden = true - self.answerCallButtonText.hidden = true - self.declineCallButton.hidden = true - self.declineCallButtonText.hidden = true + self.answerCallButton.isHidden = true + self.answerCallButtonText.isHidden = true + self.declineCallButton.isHidden = true + self.declineCallButtonText.isHidden = true self.stopTimer() @@ -340,18 +340,18 @@ public class AACallViewController: AAViewController, RTCEAGLVideoViewDelegate { if (call.peer.peerType.toNSEnum() == ACPeerType_Enum.PRIVATE) { let user = Actor.getUserWithUid(call.peer.peerId) - binder.bind(user.getNameModel(), closure: { (value: String!) -> () in + binder.bind(user.getNameModel(), closure: { (value: String?) -> () in self.peerTitle.text = value }) - binder.bind(user.getAvatarModel(), closure: { (value: ACAvatar!) -> () in + binder.bind(user.getAvatarModel(), closure: { (value: ACAvatar?) -> () in self.senderAvatar.bind(user.getNameModel().get(), id: Int(user.getId()), avatar: value) }) } else if (call.peer.peerType.toNSEnum() == ACPeerType_Enum.GROUP) { let group = Actor.getGroupWithGid(call.peer.peerId) - binder.bind(group.getNameModel(), closure: { (value: String!) -> () in + binder.bind(group.getNameModel(), closure: { (value: String?) -> () in self.peerTitle.text = value }) - binder.bind(group.getAvatarModel(), closure: { (value: ACAvatar!) -> () in + binder.bind(group.getAvatarModel(), closure: { (value: ACAvatar?) -> () in self.senderAvatar.bind(group.getNameModel().get(), id: Int(group.getId()), avatar: value) }) } @@ -370,24 +370,24 @@ public class AACallViewController: AAViewController, RTCEAGLVideoViewDelegate { // } // Local Video can be only one, so we can just keep active track reference and handle changes - binder.bind(call.ownVideoTracks, closure: { (videoTracks: ACArrayListMediaTrack!) in + binder.bind(call.ownVideoTracks, closure: { (videoTracks: ACArrayListMediaTrack?) in var needUnbind = true - if videoTracks.size() > 0 { + if videoTracks!.size() > 0 { - let track = (videoTracks.getWithInt(0) as! CocoaVideoTrack).videoTrack + let track = (videoTracks!.getWith(0) as! CocoaVideoTrack).videoTrack if self.localVideoTrack != track { if self.localVideoTrack != nil { - self.localVideoTrack.removeRenderer(self.localView) + self.localVideoTrack.remove(self.localView) } self.localVideoTrack = track self.localView.showViewAnimated() - track.addRenderer(self.localView) + track.add(self.localView) } needUnbind = false } if needUnbind { if self.localVideoTrack != nil { - self.localVideoTrack.removeRenderer(self.localView) + self.localVideoTrack.remove(self.localView) self.localVideoTrack = nil } self.localView.hideViewAnimated() @@ -396,26 +396,26 @@ public class AACallViewController: AAViewController, RTCEAGLVideoViewDelegate { // In Private Calls we can have only one video stream from other side // We will assume only one active peer connection - binder.bind(call.theirVideoTracks, closure: { (videoTracks: ACArrayListMediaTrack!) in + binder.bind(call.theirVideoTracks, closure: { (videoTracks: ACArrayListMediaTrack?) in var needUnbind = true - if videoTracks.size() > 0 { + if videoTracks!.size() > 0 { - let track = (videoTracks.getWithInt(0) as! CocoaVideoTrack).videoTrack + let track = (videoTracks!.getWith(0) as! CocoaVideoTrack).videoTrack if self.remoteVideoTrack != track { if self.remoteVideoTrack != nil { - self.remoteVideoTrack.removeRenderer(self.remoteView) + self.remoteVideoTrack.remove(self.remoteView) } self.remoteVideoTrack = track self.remoteView.showViewAnimated() self.senderAvatar.hideViewAnimated() self.peerTitle.hideViewAnimated() - track.addRenderer(self.remoteView) + track.add(self.remoteView) } needUnbind = false } if needUnbind { if self.remoteVideoTrack != nil { - self.remoteVideoTrack.removeRenderer(self.remoteView) + self.remoteVideoTrack.remove(self.remoteView) self.remoteVideoTrack = nil } self.remoteView.hideViewAnimated() @@ -430,11 +430,11 @@ public class AACallViewController: AAViewController, RTCEAGLVideoViewDelegate { } } - public override func viewWillDisappear(animated: Bool) { + open override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) - UIDevice.currentDevice().proximityMonitoringEnabled = false - UIApplication.sharedApplication().setStatusBarStyle(ActorSDK.sharedActor().style.vcStatusBarStyle, animated: true) + UIDevice.current.isProximityMonitoringEnabled = false + UIApplication.shared.setStatusBarStyle(ActorSDK.sharedActor().style.vcStatusBarStyle, animated: true) binder.unbindAll() } @@ -446,13 +446,13 @@ public class AACallViewController: AAViewController, RTCEAGLVideoViewDelegate { func startTimer() { timer?.invalidate() - timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: #selector(AACallViewController.updateTimer), userInfo: nil, repeats: true) + timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(AACallViewController.updateTimer), userInfo: nil, repeats: true) updateTimer() } func updateTimer() { if call.callStart > 0 { - let end = call.callEnd > 0 ? call.callEnd : jlong(NSDate().timeIntervalSince1970 * 1000) + let end = call.callEnd > 0 ? call.callEnd : jlong(Date().timeIntervalSince1970 * 1000) let secs = Int((end - call.callStart) / 1000) let seconds = secs % 60 @@ -469,4 +469,4 @@ public class AACallViewController: AAViewController, RTCEAGLVideoViewDelegate { timer = nil updateTimer() } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Compose/AAComposeController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Compose/AAComposeController.swift index 5640467db6..1814aa08cd 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Compose/AAComposeController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Compose/AAComposeController.swift @@ -4,7 +4,7 @@ import UIKit -public class AAComposeController: AAContactsListContentController, AAContactsListContentControllerDelegate { +open class AAComposeController: AAContactsListContentController, AAContactsListContentControllerDelegate { public override init() { super.init() @@ -15,7 +15,7 @@ public class AAComposeController: AAContactsListContentController, AAContactsLis self.navigationItem.title = AALocalized("ComposeTitle") if AADevice.isiPad { - self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationCancel"), style: UIBarButtonItemStyle.Plain, target: self, action: #selector(AAViewController.dismiss)) +// self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationCancel"), style: UIBarButtonItemStyle.plain, target: self, action: #selector(AAViewController.dismiss)) } } @@ -23,7 +23,7 @@ public class AAComposeController: AAContactsListContentController, AAContactsLis fatalError("init(coder:) has not been implemented") } - public func willAddContacts(controller: AAContactsListContentController, section: AAManagedSection) { + open func willAddContacts(_ controller: AAContactsListContentController, section: AAManagedSection) { section.custom { (r:AACustomRow) -> () in r.height = 56 @@ -53,7 +53,7 @@ public class AAComposeController: AAContactsListContentController, AAContactsLis } } - public func contactDidTap(controller: AAContactsListContentController, contact: ACContact) -> Bool { + open func contactDidTap(_ controller: AAContactsListContentController, contact: ACContact) -> Bool { if let customController = ActorSDK.sharedActor().delegate.actorControllerForConversation(ACPeer_userWithInt_(contact.uid)) { navigateDetail(customController) } else { @@ -61,4 +61,4 @@ public class AAComposeController: AAContactsListContentController, AAContactsLis } return false } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Compose/AAGroupCreateViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Compose/AAGroupCreateViewController.swift index 109d098d27..be5d9402cb 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Compose/AAGroupCreateViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Compose/AAGroupCreateViewController.swift @@ -4,17 +4,17 @@ import Foundation -public class AAGroupCreateViewController: AAViewController, UITextFieldDelegate { +open class AAGroupCreateViewController: AAViewController, UITextFieldDelegate { - private let isChannel: Bool - private var addPhotoButton = UIButton() - private var avatarImageView = UIImageView() - private var hint = UILabel() + fileprivate let isChannel: Bool + fileprivate var addPhotoButton = UIButton() + fileprivate var avatarImageView = UIImageView() + fileprivate var hint = UILabel() - private var groupName = UITextField() - private var groupNameFieldSeparator = UIView() + fileprivate var groupName = UITextField() + fileprivate var groupNameFieldSeparator = UIView() - private var image: UIImage? + fileprivate var image: UIImage? public init(isChannel: Bool) { self.isChannel = isChannel @@ -25,16 +25,16 @@ public class AAGroupCreateViewController: AAViewController, UITextFieldDelegate self.navigationItem.title = AALocalized("CreateGroupTitle") } if AADevice.isiPad { - self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationCancel"), style: UIBarButtonItemStyle.Plain, target: self, action: #selector(AAViewController.dismiss)) +// self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationCancel"), style: UIBarButtonItemStyle.plain, target: self, action: #selector(AAViewController.dismiss)) } - self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationNext"), style: UIBarButtonItemStyle.Done, target: self, action: #selector(AAGroupCreateViewController.doNext)) + self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationNext"), style: UIBarButtonItemStyle.done, target: self, action: #selector(AAGroupCreateViewController.doNext)) } public required init(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - public override func viewDidLoad() { + open override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = appStyle.vcBgColor @@ -46,52 +46,52 @@ public class AAGroupCreateViewController: AAViewController, UITextFieldDelegate UIGraphicsBeginImageContextWithOptions(CGSize(width: 110, height: 110), false, 0.0); let context = UIGraphicsGetCurrentContext(); - CGContextSetFillColorWithColor(context, appStyle.composeAvatarBgColor.CGColor); - CGContextFillEllipseInRect(context, CGRectMake(0.0, 0.0, 110.0, 110.0)); - CGContextSetStrokeColorWithColor(context, appStyle.composeAvatarBorderColor.CGColor); - CGContextSetLineWidth(context, 1.0); - CGContextStrokeEllipseInRect(context, CGRectMake(0.5, 0.5, 109.0, 109.0)); + context?.setFillColor(appStyle.composeAvatarBgColor.cgColor); + context?.fillEllipse(in: CGRect(x: 0.0, y: 0.0, width: 110.0, height: 110.0)); + context?.setStrokeColor(appStyle.composeAvatarBorderColor.cgColor); + context?.setLineWidth(1.0); + context?.strokeEllipse(in: CGRect(x: 0.5, y: 0.5, width: 109.0, height: 109.0)); let buttonImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); - addPhotoButton.exclusiveTouch = true - addPhotoButton.setBackgroundImage(buttonImage, forState: UIControlState.Normal) - addPhotoButton.addTarget(self, action: #selector(AAGroupCreateViewController.photoTap), forControlEvents: UIControlEvents.TouchUpInside) + addPhotoButton.isExclusiveTouch = true + addPhotoButton.setBackgroundImage(buttonImage, for: UIControlState()) + addPhotoButton.addTarget(self, action: #selector(AAGroupCreateViewController.photoTap), for: UIControlEvents.touchUpInside) let addPhotoLabelFirst = UILabel() addPhotoLabelFirst.text = AALocalized("ActionAddPhoto1") - addPhotoLabelFirst.font = UIFont.systemFontOfSize(15.0) - addPhotoLabelFirst.backgroundColor = UIColor.clearColor() + addPhotoLabelFirst.font = UIFont.systemFont(ofSize: 15.0) + addPhotoLabelFirst.backgroundColor = UIColor.clear addPhotoLabelFirst.textColor = appStyle.composeAvatarTextColor addPhotoLabelFirst.sizeToFit() let addPhotoLabelSecond = UILabel() addPhotoLabelSecond.text = AALocalized("ActionAddPhoto2") - addPhotoLabelSecond.font = UIFont.systemFontOfSize(15.0) - addPhotoLabelSecond.backgroundColor = UIColor.clearColor() + addPhotoLabelSecond.font = UIFont.systemFont(ofSize: 15.0) + addPhotoLabelSecond.backgroundColor = UIColor.clear addPhotoLabelSecond.textColor = appStyle.composeAvatarTextColor addPhotoLabelSecond.sizeToFit() addPhotoButton.addSubview(addPhotoLabelFirst) addPhotoButton.addSubview(addPhotoLabelSecond) - addPhotoLabelFirst.frame = CGRectIntegral(CGRectMake((80 - addPhotoLabelFirst.frame.size.width) / 2, 22, addPhotoLabelFirst.frame.size.width, addPhotoLabelFirst.frame.size.height)); - addPhotoLabelSecond.frame = CGRectIntegral(CGRectMake((80 - addPhotoLabelSecond.frame.size.width) / 2, 22 + 22, addPhotoLabelSecond.frame.size.width, addPhotoLabelSecond.frame.size.height)); + addPhotoLabelFirst.frame = CGRect(x: (80 - addPhotoLabelFirst.frame.size.width) / 2, y: 22, width: addPhotoLabelFirst.frame.size.width, height: addPhotoLabelFirst.frame.size.height).integral; + addPhotoLabelSecond.frame = CGRect(x: (80 - addPhotoLabelSecond.frame.size.width) / 2, y: 22 + 22, width: addPhotoLabelSecond.frame.size.width, height: addPhotoLabelSecond.frame.size.height).integral; groupName.backgroundColor = appStyle.vcBgColor groupName.textColor = ActorSDK.sharedActor().style.cellTextColor - groupName.font = UIFont.systemFontOfSize(20) - groupName.keyboardType = UIKeyboardType.Default - groupName.returnKeyType = UIReturnKeyType.Next + groupName.font = UIFont.systemFont(ofSize: 20) + groupName.keyboardType = UIKeyboardType.default + groupName.returnKeyType = UIReturnKeyType.next if isChannel { groupName.attributedPlaceholder = NSAttributedString(string: AALocalized("CreateChannelNamePlaceholder"), attributes: [NSForegroundColorAttributeName: ActorSDK.sharedActor().style.vcHintColor]) } else { groupName.attributedPlaceholder = NSAttributedString(string: AALocalized("CreateGroupNamePlaceholder"), attributes: [NSForegroundColorAttributeName: ActorSDK.sharedActor().style.vcHintColor]) } groupName.delegate = self - groupName.contentVerticalAlignment = UIControlContentVerticalAlignment.Center - groupName.autocapitalizationType = UITextAutocapitalizationType.Words - groupName.keyboardAppearance = appStyle.isDarkApp ? .Dark : .Light + groupName.contentVerticalAlignment = UIControlContentVerticalAlignment.center + groupName.autocapitalizationType = UITextAutocapitalizationType.words + groupName.keyboardAppearance = appStyle.isDarkApp ? .dark : .light groupNameFieldSeparator.backgroundColor = appStyle.vcSeparatorColor @@ -101,25 +101,25 @@ public class AAGroupCreateViewController: AAViewController, UITextFieldDelegate hint.text = AALocalized("CreateGroupHint") } - hint.font = UIFont.systemFontOfSize(15) - hint.lineBreakMode = .ByWordWrapping + hint.font = UIFont.systemFont(ofSize: 15) + hint.lineBreakMode = .byWordWrapping hint.numberOfLines = 0 hint.textColor = ActorSDK.sharedActor().style.vcHintColor } - public override func viewDidLayoutSubviews() { + open override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() - avatarImageView.frame = CGRectMake(20, 20 + 66, 80, 80) + avatarImageView.frame = CGRect(x: 20, y: 20 + 66, width: 80, height: 80) addPhotoButton.frame = avatarImageView.frame - hint.frame = CGRectMake(120, 20 + 66, view.width - 140, 80) + hint.frame = CGRect(x: 120, y: 20 + 66, width: view.width - 140, height: 80) - groupName.frame = CGRectMake(20, 106 + 66, view.width - 20, 56.0) - groupNameFieldSeparator.frame = CGRectMake(20, 156 + 66, view.width - 20, 0.5) + groupName.frame = CGRect(x: 20, y: 106 + 66, width: view.width - 20, height: 56.0) + groupNameFieldSeparator.frame = CGRect(x: 20, y: 156 + 66, width: view.width - 20, height: 0.5) } - public func photoTap() { - let hasCamera = UIImagePickerController.isSourceTypeAvailable(UIImagePickerControllerSourceType.Camera) + open func photoTap() { + let hasCamera = UIImagePickerController.isSourceTypeAvailable(UIImagePickerControllerSourceType.camera) self.showActionSheet(hasCamera ? ["PhotoCamera", "PhotoLibrary"] : ["PhotoLibrary"], cancelButton: "AlertCancel", destructButton: self.avatarImageView.image != nil ? "PhotoRemove" : nil, @@ -139,17 +139,17 @@ public class AAGroupCreateViewController: AAViewController, UITextFieldDelegate }) } - public override func viewWillAppear(animated: Bool) { + open override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) groupName.becomeFirstResponder() } - public func textFieldShouldReturn(textField: UITextField) -> Bool { + open func textFieldShouldReturn(_ textField: UITextField) -> Bool { doNext() return false } - public func doNext() { + open func doNext() { let title = groupName.text!.trim() if (title.length == 0) { shakeView(groupName, originalX: groupName.frame.origin.x) @@ -159,11 +159,11 @@ public class AAGroupCreateViewController: AAViewController, UITextFieldDelegate groupName.resignFirstResponder() if isChannel { - executePromise(Actor.createChannelWithTitle(title, withAvatar: nil)).then({ (gid: JavaLangInteger!) in + executePromise(Actor.createChannel(withTitle: title, withAvatar: nil)).then({ (gid: JavaLangInteger!) in self.navigateNext(AAGroupTypeViewController(gid: Int(gid.intValue()), isCreation: true), removeCurrent: true) }) } else { navigateNext(GroupMembersController(title: title, image: image), removeCurrent: true) } } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Compose/AAGroupMembersController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Compose/AAGroupMembersController.swift index 7eefccf47e..9b0e44d3c5 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Compose/AAGroupMembersController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Compose/AAGroupMembersController.swift @@ -4,15 +4,15 @@ import UIKit -public class GroupMembersController: AAContactsListContentController, AAContactsListContentControllerDelegate, CLTokenInputViewDelegate { +open class GroupMembersController: AAContactsListContentController, AAContactsListContentControllerDelegate, CLTokenInputViewDelegate { - private var groupTitle: String! - private var groupImage: UIImage? + fileprivate var groupTitle: String! + fileprivate var groupImage: UIImage? - private var tokenView = CLTokenInputView() - private var tokenViewHeight: CGFloat = 48 + fileprivate var tokenView = CLTokenInputView() + fileprivate var tokenViewHeight: CGFloat = 48 - private var selected = [TokenRef]() + fileprivate var selected = [TokenRef]() public init(title: String, image: UIImage?) { super.init() @@ -25,17 +25,17 @@ public class GroupMembersController: AAContactsListContentController, AAContacts navigationItem.title = AALocalized("CreateGroupMembersTitle") if AADevice.isiPad { - self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationCancel"), style: UIBarButtonItemStyle.Plain, target: self, action: #selector(dismiss)) +// self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationCancel"), style: UIBarButtonItemStyle.plain, target: self, action: #selector(self.dismiss)) } - navigationItem.rightBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationDone"), style: UIBarButtonItemStyle.Done, target: self, action: #selector(GroupMembersController.doNext)) + navigationItem.rightBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationDone"), style: UIBarButtonItemStyle.done, target: self, action: #selector(GroupMembersController.doNext)) } public required init(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - public override func tableDidLoad() { + open override func tableDidLoad() { super.tableDidLoad() tokenView.delegate = self @@ -45,29 +45,29 @@ public class GroupMembersController: AAContactsListContentController, AAContacts tokenView.tintColor = appStyle.vcTokenTintColor tokenView.fieldName = "" - let placeholder = AALocalized("CreateGroupMembersPlaceholders") + let placeholder = AALocalized("CreateGroupMembersPlaceholders")! let attributedPlaceholder = NSMutableAttributedString(string: placeholder) attributedPlaceholder.addAttribute(NSForegroundColorAttributeName, value: appStyle.vcHintColor, range: NSRange(location: 0, length: placeholder.length)) tokenView.placeholderAttributedText = attributedPlaceholder self.view.addSubview(tokenView) - tableView.keyboardDismissMode = UIScrollViewKeyboardDismissMode.OnDrag + tableView.keyboardDismissMode = UIScrollViewKeyboardDismissMode.onDrag } - public func contactDidTap(controller: AAContactsListContentController, contact: ACContact) -> Bool { + open func contactDidTap(_ controller: AAContactsListContentController, contact: ACContact) -> Bool { for i in 0..(lhs: T?, rhs: T?) -> Bool { + switch (lhs, rhs) { + case let (l?, r?): + return l < r + case (nil, _?): + return true + default: + return false + } +} -public class AAContactsViewController: AAContactsListContentController, AAContactsListContentControllerDelegate, UIAlertViewDelegate, MFMessageComposeViewControllerDelegate, MFMailComposeViewControllerDelegate { +fileprivate func > (lhs: T?, rhs: T?) -> Bool { + switch (lhs, rhs) { + case let (l?, r?): + return l > r + default: + return rhs < lhs + } +} + + +open class AAContactsViewController: AAContactsListContentController, AAContactsListContentControllerDelegate, UIAlertViewDelegate, MFMessageComposeViewControllerDelegate, MFMailComposeViewControllerDelegate { var inviteText: String { get { @@ -19,13 +39,13 @@ public class AAContactsViewController: AAContactsListContentController, AAContac public override init() { super.init() - content = ACAllEvents_Main.CONTACTS() + content = ACAllEvents_Main.contacts() tabBarItem = UITabBarItem(title: "TabPeople", img: "TabIconContacts", selImage: "TabIconContactsHighlighted") navigationItem.title = AALocalized("TabPeople") - navigationItem.backBarButtonItem = UIBarButtonItem(title: AALocalized("ContactsBack"), style: UIBarButtonItemStyle.Plain, target: nil, action: nil) - navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.Add, target: self, action: #selector(AAContactsViewController.findContact)) + navigationItem.backBarButtonItem = UIBarButtonItem(title: AALocalized("ContactsBack"), style: UIBarButtonItemStyle.plain, target: nil, action: nil) + navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.add, target: self, action: #selector(AAContactsViewController.findContact)) delegate = self } @@ -34,7 +54,7 @@ public class AAContactsViewController: AAContactsListContentController, AAContac fatalError("init(coder:) has not been implemented") } - public func contactDidTap(controller: AAContactsListContentController, contact: ACContact) -> Bool { + open func contactDidTap(_ controller: AAContactsListContentController, contact: ACContact) -> Bool { if let customController = ActorSDK.sharedActor().delegate.actorControllerForConversation(ACPeer_userWithInt_(contact.uid)) { navigateDetail(customController) @@ -45,7 +65,7 @@ public class AAContactsViewController: AAContactsListContentController, AAContac return true } - public func willAddContacts(controller: AAContactsListContentController, section: AAManagedSection) { + open func willAddContacts(_ controller: AAContactsListContentController, section: AAManagedSection) { section.custom { (r: AACustomRow) -> () in @@ -85,39 +105,39 @@ public class AAContactsViewController: AAContactsListContentController, AAContac } } - if SLComposeViewController.isAvailableForServiceType(SLServiceTypeTencentWeibo) { + if SLComposeViewController.isAvailable(forServiceType: SLServiceTypeTencentWeibo) { builder.add("Tencent Weibo") { () -> () in - let vc = SLComposeViewController(forServiceType: SLServiceTypeTencentWeibo) + let vc = SLComposeViewController(forServiceType: SLServiceTypeTencentWeibo)! vc.setInitialText(self.inviteText) - self.presentViewController(vc, animated: true, completion: nil) + self.present(vc, animated: true, completion: nil) } } - if SLComposeViewController.isAvailableForServiceType(SLServiceTypeSinaWeibo) { + if SLComposeViewController.isAvailable(forServiceType: SLServiceTypeSinaWeibo) { builder.add("Sina Weibo") { () -> () in - let vc = SLComposeViewController(forServiceType: SLServiceTypeSinaWeibo) + let vc = SLComposeViewController(forServiceType: SLServiceTypeSinaWeibo)! vc.setInitialText(self.inviteText) - self.presentViewController(vc, animated: true, completion: nil) + self.present(vc, animated: true, completion: nil) } } - if SLComposeViewController.isAvailableForServiceType(SLServiceTypeTwitter) { + if SLComposeViewController.isAvailable(forServiceType: SLServiceTypeTwitter) { builder.add("Twitter") { () -> () in - let vc = SLComposeViewController(forServiceType: SLServiceTypeTwitter) + let vc = SLComposeViewController(forServiceType: SLServiceTypeTwitter)! vc.setInitialText(self.inviteText) - self.presentViewController(vc, animated: true, completion: nil) + self.present(vc, animated: true, completion: nil) } } - if SLComposeViewController.isAvailableForServiceType(SLServiceTypeFacebook) { + if SLComposeViewController.isAvailable(forServiceType: SLServiceTypeFacebook) { builder.add("Facebook") { () -> () in - let vc = SLComposeViewController(forServiceType: SLServiceTypeFacebook) - vc.addURL(NSURL(string: ActorSDK.sharedActor().inviteUrl)) - self.presentViewController(vc, animated: true, completion: nil) + let vc = SLComposeViewController(forServiceType: SLServiceTypeFacebook)! + vc.add(URL(string: ActorSDK.sharedActor().inviteUrl)) + self.present(vc, animated: true, completion: nil) } } - let view = self.tableView.cellForRowAtIndexPath(NSIndexPath(forRow: 1, inSection: 0))!.contentView + let view = self.tableView.cellForRow(at: IndexPath(row: 1, section: 0))!.contentView self.showActionSheet(builder.items, cancelButton: "AlertCancel", destructButton: nil, sourceView: view, sourceRect: view.bounds, tapClosure: builder.tapClosure) @@ -128,7 +148,7 @@ public class AAContactsViewController: AAContactsListContentController, AAContac // Searching for contact - public func findContact() { + open func findContact() { startEditField { (c) -> () in c.title = "FindTitle" @@ -137,9 +157,9 @@ public class AAContactsViewController: AAContactsListContentController, AAContac c.hint = "FindHint" c.fieldHint = "FindFieldHint" - c.fieldAutocapitalizationType = .None - c.fieldAutocorrectionType = .No - c.fieldReturnKey = .Search + c.fieldAutocapitalizationType = .none + c.fieldAutocorrectionType = .no + c.fieldReturnKey = .search c.didDoneTap = { (t, c) -> () in @@ -147,18 +167,18 @@ public class AAContactsViewController: AAContactsListContentController, AAContac return } - self.executeSafeOnlySuccess(Actor.findUsersCommandWithQuery(t), successBlock: { (val) -> Void in + self.executeSafeOnlySuccess(Actor.findUsersCommand(withQuery: t), successBlock: { (val) -> Void in var user: ACUserVM? = nil if let users = val as? IOSObjectArray { if Int(users.length()) > 0 { - if let tempUser = users.objectAtIndex(0) as? ACUserVM { + if let tempUser = users.object(at: 0) as? ACUserVM { user = tempUser } } } if user != nil { - c.execute(Actor.addContactCommandWithUid(user!.getId())!, successBlock: { (val) -> Void in + c.execute(Actor.addContactCommand(withUid: user!.getId())!, successBlock: { (val) -> Void in if let customController = ActorSDK.sharedActor().delegate.actorControllerForConversation(ACPeer_userWithInt_(user!.getId())) { self.navigateDetail(customController) } else { @@ -181,24 +201,24 @@ public class AAContactsViewController: AAContactsListContentController, AAContac } } - public func alertView(alertView: UIAlertView, clickedButtonAtIndex buttonIndex: Int) { + open func alertView(_ alertView: UIAlertView, clickedButtonAt buttonIndex: Int) { if buttonIndex == 1 { - let textField = alertView.textFieldAtIndex(0)! + let textField = alertView.textField(at: 0)! if textField.text?.length > 0 { - execute(Actor.findUsersCommandWithQuery(textField.text), successBlock: { (val) -> () in + execute(Actor.findUsersCommand(withQuery: textField.text), successBlock: { (val) -> () in var user: ACUserVM? user = val as? ACUserVM if user == nil { if let users = val as? IOSObjectArray { if Int(users.length()) > 0 { - if let tempUser = users.objectAtIndex(0) as? ACUserVM { + if let tempUser = users.object(at: 0) as? ACUserVM { user = tempUser } } } } if user != nil { - self.execute(Actor.addContactCommandWithUid(user!.getId())!, successBlock: { (val) -> () in + self.execute(Actor.addContactCommand(withUid: user!.getId())!, successBlock: { (val) -> () in if let customController = ActorSDK.sharedActor().delegate.actorControllerForConversation(ACPeer_userWithInt_(user!.getId())) { self.navigateDetail(customController) } else { @@ -219,7 +239,7 @@ public class AAContactsViewController: AAContactsListContentController, AAContac // Email Invitation - public func showEmailInvitation(recipients: [String]?) { + open func showEmailInvitation(_ recipients: [String]?) { if MFMailComposeViewController.canSendMail() { let messageComposeController = MFMailComposeViewController() @@ -231,30 +251,30 @@ public class AAContactsViewController: AAContactsListContentController, AAContac // TODO: Replace with bigger text messageComposeController.setMessageBody(inviteText, isHTML: false) messageComposeController.setToRecipients(recipients) - presentViewController(messageComposeController, animated: true, completion: nil) + present(messageComposeController, animated: true, completion: nil) } } - public func mailComposeController(controller: MFMailComposeViewController, didFinishWithResult result: MFMailComposeResult, error: NSError?) { - controller.dismissViewControllerAnimated(true, completion: nil) + open func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) { + controller.dismiss(animated: true, completion: nil) } // SMS Invitation - public func showSmsInvitation() { + open func showSmsInvitation() { self.showSmsInvitation(nil) } - public func showSmsInvitation(recipients: [String]?) { + open func showSmsInvitation(_ recipients: [String]?) { if MFMessageComposeViewController.canSendText() { let messageComposeController = MFMessageComposeViewController() messageComposeController.messageComposeDelegate = self messageComposeController.body = inviteText messageComposeController.recipients = recipients - presentViewController(messageComposeController, animated: true, completion: nil) + present(messageComposeController, animated: true, completion: nil) } } - @objc public func messageComposeViewController(controller: MFMessageComposeViewController, didFinishWithResult result: MessageComposeResult) { - controller.dismissViewControllerAnimated(true, completion: nil) + @objc open func messageComposeViewController(_ controller: MFMessageComposeViewController, didFinishWith result: MessageComposeResult) { + controller.dismiss(animated: true, completion: nil) } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Contacts List/AAContactsListContentController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Contacts List/AAContactsListContentController.swift index 0a12abb738..61119c1e3e 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Contacts List/AAContactsListContentController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Contacts List/AAContactsListContentController.swift @@ -4,22 +4,22 @@ import Foundation -public class AAContactsListContentController: AAContentTableController { +open class AAContactsListContentController: AAContentTableController { - public var delegate: AAContactsListContentControllerDelegate? - public var isSearchAutoHide: Bool = true - public var contactRows: AABindedRows! - public var searchEnabled: Bool = true + open var delegate: AAContactsListContentControllerDelegate? + open var isSearchAutoHide: Bool = true + open var contactRows: AABindedRows! + open var searchEnabled: Bool = true public init() { - super.init(style: .Plain) + super.init(style: .plain) } public required init(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - public override func tableDidLoad() { + open override func tableDidLoad() { if searchEnabled { search(AAContactCell.self) { (s) -> () in @@ -78,7 +78,7 @@ public class AAContactsListContentController: AAContentTableController { action2Selector: nil) } - public override func viewWillAppear(animated: Bool) { + open override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) binder.bind(Actor.getAppState().isContactsEmpty, closure: { (value: Any?) -> () in @@ -91,4 +91,4 @@ public class AAContactsListContentController: AAContentTableController { } }) } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Contacts List/AAContactsListContentControllerDelegate.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Contacts List/AAContactsListContentControllerDelegate.swift index 2af5b3573c..10235d0790 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Contacts List/AAContactsListContentControllerDelegate.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Contacts List/AAContactsListContentControllerDelegate.swift @@ -6,27 +6,27 @@ import Foundation public protocol AAContactsListContentControllerDelegate { - func willAddContacts(controller: AAContactsListContentController, section: AAManagedSection) + func willAddContacts(_ controller: AAContactsListContentController, section: AAManagedSection) - func didAddContacts(controller: AAContactsListContentController, section: AAManagedSection) + func didAddContacts(_ controller: AAContactsListContentController, section: AAManagedSection) - func contactDidTap(controller: AAContactsListContentController, contact: ACContact) -> Bool + func contactDidTap(_ controller: AAContactsListContentController, contact: ACContact) -> Bool - func contactDidBind(controller: AAContactsListContentController, contact: ACContact, cell: AAContactCell) + func contactDidBind(_ controller: AAContactsListContentController, contact: ACContact, cell: AAContactCell) } public extension AAContactsListContentControllerDelegate { - public func willAddContacts(controller: AAContactsListContentController, section: AAManagedSection) { + public func willAddContacts(_ controller: AAContactsListContentController, section: AAManagedSection) { // Do Nothing } - public func didAddContacts(controller: AAContactsListContentController, section: AAManagedSection) { + public func didAddContacts(_ controller: AAContactsListContentController, section: AAManagedSection) { // Do Nothing } - public func contactDidBind(controller: AAContactsListContentController, contact: ACContact, cell: AAContactCell) { + public func contactDidBind(_ controller: AAContactsListContentController, contact: ACContact, cell: AAContactCell) { // Do Nothing } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Contacts List/Cells/AAContactActionCell.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Contacts List/Cells/AAContactActionCell.swift index 0f8db0723a..1ffd7a496c 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Contacts List/Cells/AAContactActionCell.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Contacts List/Cells/AAContactActionCell.swift @@ -4,18 +4,18 @@ import Foundation -public class AAContactActionCell: AATableViewCell { +open class AAContactActionCell: AATableViewCell { - public let titleView = YYLabel() - public let iconView = UIImageView() + open let titleView = YYLabel() + open let iconView = UIImageView() public override init(style: UITableViewCellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) - titleView.font = UIFont.systemFontOfSize(18) + titleView.font = UIFont.systemFont(ofSize: 18) titleView.textColor = ActorSDK.sharedActor().style.cellTintColor titleView.displaysAsynchronously = true - iconView.contentMode = UIViewContentMode.Center + iconView.contentMode = UIViewContentMode.center self.contentView.addSubview(titleView) self.contentView.addSubview(iconView) } @@ -24,15 +24,15 @@ public class AAContactActionCell: AATableViewCell { fatalError("init(coder:) has not been implemented") } - public func bind(icon: String, actionTitle: String) { + open func bind(_ icon: String, actionTitle: String) { titleView.text = actionTitle iconView.image = UIImage.bundled(icon)?.tintImage(ActorSDK.sharedActor().style.cellTintColor) } - public override func layoutSubviews() { + open override func layoutSubviews() { super.layoutSubviews() let width = self.contentView.frame.width; - iconView.frame = CGRectMake(30, 8, 40, 40); - titleView.frame = CGRectMake(80, 8, width - 80 - 14, 40); + iconView.frame = CGRect(x: 30, y: 8, width: 40, height: 40); + titleView.frame = CGRect(x: 80, y: 8, width: width - 80 - 14, height: 40); } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Contacts List/Cells/AAContactCell.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Contacts List/Cells/AAContactCell.swift index 86acf27180..e7ba4f02ad 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Contacts List/Cells/AAContactCell.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Contacts List/Cells/AAContactCell.swift @@ -5,31 +5,31 @@ import Foundation import UIKit -public class AAContactCell : AATableViewCell, AABindedCell, AABindedSearchCell { +open class AAContactCell : AATableViewCell, AABindedCell, AABindedSearchCell { public typealias BindData = ACContact - public static func bindedCellHeight(table: AAManagedTable, item: BindData) -> CGFloat { + open static func bindedCellHeight(_ table: AAManagedTable, item: BindData) -> CGFloat { return 56 } - public static func bindedCellHeight(item: BindData) -> CGFloat { + open static func bindedCellHeight(_ item: BindData) -> CGFloat { return 56 } - public let avatarView = AAAvatarView() - public let shortNameView = YYLabel() - public let titleView = YYLabel() + open let avatarView = AAAvatarView() + open let shortNameView = YYLabel() + open let titleView = YYLabel() public override init(style: UITableViewCellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) - titleView.font = UIFont.systemFontOfSize(18) + titleView.font = UIFont.systemFont(ofSize: 18) titleView.textColor = appStyle.contactTitleColor titleView.displaysAsynchronously = true - shortNameView.font = UIFont.boldSystemFontOfSize(18) - shortNameView.textAlignment = NSTextAlignment.Center + shortNameView.font = UIFont.boldSystemFont(ofSize: 18) + shortNameView.textAlignment = NSTextAlignment.center shortNameView.textColor = appStyle.contactTitleColor shortNameView.displaysAsynchronously = true @@ -42,23 +42,23 @@ public class AAContactCell : AATableViewCell, AABindedCell, AABindedSearchCell { fatalError("init(coder:) has not been implemented") } - public func bind(item: ACContact, search: String?) { + open func bind(_ item: ACContact, search: String?) { bind(item) } - public func bind(item: ACContact, table: AAManagedTable, index: Int, totalCount: Int) { + open func bind(_ item: ACContact, table: AAManagedTable, index: Int, totalCount: Int) { bind(item) } - func bind(item: ACContact) { + func bind(_ item: ACContact) { avatarView.bind(item.name, id: Int(item.uid), avatar: item.avatar); titleView.text = item.name; - shortNameView.hidden = true + shortNameView.isHidden = true } - func bindDisabled(disabled: Bool) { + func bindDisabled(_ disabled: Bool) { if disabled { titleView.alpha = 0.5 avatarView.alpha = 0.5 @@ -68,11 +68,11 @@ public class AAContactCell : AATableViewCell, AABindedCell, AABindedSearchCell { } } - public override func layoutSubviews() { + open override func layoutSubviews() { super.layoutSubviews() let width = self.contentView.frame.width; - shortNameView.frame = CGRectMake(0, 8, 30, 40); - avatarView.frame = CGRectMake(30, 8, 44, 44); - titleView.frame = CGRectMake(80, 8, width - 80 - 14, 40); + shortNameView.frame = CGRect(x: 0, y: 8, width: 30, height: 40); + avatarView.frame = CGRect(x: 30, y: 8, width: 44, height: 44); + titleView.frame = CGRect(x: 80, y: 8, width: width - 80 - 14, height: 40); } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/AABubbles.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/AABubbles.swift index b7f62aefe5..29a5cd81dc 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/AABubbles.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/AABubbles.swift @@ -28,7 +28,7 @@ class AABubbles { static var layouters: [AABubbleLayouter] = builtInLayouters - class func layouterForMessage(message: ACMessage) -> AABubbleLayouter { + class func layouterForMessage(_ message: ACMessage) -> AABubbleLayouter { for layouter in layouters { if layouter.isSuitable(message) { return layouter @@ -37,7 +37,7 @@ class AABubbles { return textLayouter } - class func buildLayout(peer: ACPeer, message: ACMessage) -> AACellLayout { + class func buildLayout(_ peer: ACPeer, message: ACMessage) -> AACellLayout { for layouter in layouters { if (layouter.isSuitable(message)) { @@ -48,4 +48,4 @@ class AABubbles { return textLayouter.buildLayout(peer, message: message) } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/AAConversationContentController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/AAConversationContentController.swift index 0d2cf82b08..158e030240 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/AAConversationContentController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/AAConversationContentController.swift @@ -8,47 +8,47 @@ import MediaPlayer import AVKit import AVFoundation -public class AAConversationContentController: SLKTextViewController, ARDisplayList_AppleChangeListener { +open class AAConversationContentController: SLKTextViewController, ARDisplayList_AppleChangeListener { - public let peer: ACPeer + open let peer: ACPeer - private let delayLoad = false + fileprivate let delayLoad = false - private let binder = AABinder() + fileprivate let binder = AABinder() - private var displayList: ARBindedDisplayList! - private var isStarted: Bool = AADevice.isiPad - private var isVisible: Bool = false - private var isLoaded: Bool = false - private var isLoadedAfter: Bool = false - private var unreadIndex: Int? = nil - private let collectionViewLayout = AAMessagesFlowLayout() - private var prevCount: Int = 0 - private var unreadMessageId: jlong = 0 + fileprivate var displayList: ARBindedDisplayList! + fileprivate var isStarted: Bool = AADevice.isiPad + fileprivate var isVisible: Bool = false + fileprivate var isLoaded: Bool = false + fileprivate var isLoadedAfter: Bool = false + fileprivate var unreadIndex: Int? = nil + fileprivate let collectionViewLayout = AAMessagesFlowLayout() + fileprivate var prevCount: Int = 0 + fileprivate var unreadMessageId: jlong = 0 - private var isUpdating: Bool = false - private var isBinded: Bool = false - private var pendingUpdates = [ARAppleListUpdate]() - private var readDate: jlong = 0 - private var receiveDate: jlong = 0 + fileprivate var isUpdating: Bool = false + fileprivate var isBinded: Bool = false + fileprivate var pendingUpdates = [ARAppleListUpdate]() + fileprivate var readDate: jlong = 0 + fileprivate var receiveDate: jlong = 0 // Audio notes - public var voicePlayer : AAModernConversationAudioPlayer! - public var voiceContext : AAModernViewInlineMediaContext! + open var voicePlayer : AAModernConversationAudioPlayer! + open var voiceContext : AAModernViewInlineMediaContext! - public var currentAudioFileId: jlong = 0 - public var voicesCache: Dictionary = Dictionary() + open var currentAudioFileId: jlong = 0 + open var voicesCache: Dictionary = Dictionary() public init(peer: ACPeer) { self.peer = peer super.init(collectionViewLayout: collectionViewLayout) - self.collectionView.backgroundColor = UIColor.clearColor() + self.collectionView.backgroundColor = UIColor.clear self.collectionView.alwaysBounceVertical = true for layout in AABubbles.layouters { - self.collectionView.registerClass(layout.cellClass(), forCellWithReuseIdentifier: layout.cellReuseId()) + self.collectionView.register(layout.cellClass(), forCellWithReuseIdentifier: layout.cellReuseId()) } } @@ -58,7 +58,7 @@ public class AAConversationContentController: SLKTextViewController, ARDisplayLi // Controller and UI lifecycle - public override func viewDidLoad() { + open override func viewDidLoad() { super.viewDidLoad() if (self.displayList == nil) { @@ -66,7 +66,7 @@ public class AAConversationContentController: SLKTextViewController, ARDisplayLi } } - public override func viewWillAppear(animated: Bool) { + open override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) isVisible = true @@ -83,7 +83,7 @@ public class AAConversationContentController: SLKTextViewController, ARDisplayLi self.collectionView.alpha = 0 } - dispatch_async(dispatch_get_main_queue(),{ + DispatchQueue.main.async(execute: { // What if controller is already closed? if (!self.isVisible) { return @@ -91,23 +91,23 @@ public class AAConversationContentController: SLKTextViewController, ARDisplayLi self.isStarted = true - UIView.animateWithDuration(0.6, animations: { () -> Void in self.collectionView.alpha = 1 }, completion: { (comp) -> Void in }) + UIView.animate(withDuration: 0.6, animations: { () -> Void in self.collectionView.alpha = 1 }, completion: { (comp) -> Void in }) self.tryBind() }) } else { self.isStarted = true - UIView.animateWithDuration(0.6, animations: { () -> Void in self.collectionView.alpha = 1 }, completion: { (comp) -> Void in }) + UIView.animate(withDuration: 0.6, animations: { () -> Void in self.collectionView.alpha = 1 }, completion: { (comp) -> Void in }) tryBind() } } - private func tryBind() { + fileprivate func tryBind() { if !self.isBinded { - self.binder.bind(Actor.getConversationVMWithACPeer(peer).getReadDate()) { (val: JavaLangLong!) in + self.binder.bind(Actor.getConversationVM(with: peer).getReadDate()) { (val: JavaLangLong?) in let nReadDate = val!.longLongValue() let oReadDate = self.readDate @@ -117,7 +117,7 @@ public class AAConversationContentController: SLKTextViewController, ARDisplayLi self.messageStatesUpdated(oReadDate, end: nReadDate) } } - self.binder.bind(Actor.getConversationVMWithACPeer(peer).getReceiveDate()) { (val: JavaLangLong!) in + self.binder.bind(Actor.getConversationVM(with: peer).getReceiveDate()) { (val: JavaLangLong?) in let nReceiveDate = val!.longLongValue() let oReceiveDate = self.receiveDate @@ -139,45 +139,45 @@ public class AAConversationContentController: SLKTextViewController, ARDisplayLi } - public func buildCell(collectionView: UICollectionView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell { + open func buildCell(_ collectionView: UICollectionView, cellForRowAtIndexPath indexPath: IndexPath) -> UICollectionViewCell { let list = getProcessedList() - let layout = list!.layouts[indexPath.row] - let cell = collectionView.dequeueReusableCellWithReuseIdentifier(layout.layouter.cellReuseId(), forIndexPath: indexPath) + let layout = list!.layouts[(indexPath as NSIndexPath).row] + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: layout.layouter.cellReuseId(), for: indexPath) (cell as! AABubbleCell).setConfig(peer, controller: self) return cell } - public func bindCell(collectionView: UICollectionView, cellForRowAtIndexPath indexPath: NSIndexPath, cell: UICollectionViewCell) { + open func bindCell(_ collectionView: UICollectionView, cellForRowAtIndexPath indexPath: IndexPath, cell: UICollectionViewCell) { let list = getProcessedList() - let message = list!.items[indexPath.row] - let setting = list!.cellSettings[indexPath.row] - let layout = list!.layouts[indexPath.row] + let message = list!.items[(indexPath as NSIndexPath).row] + let setting = list!.cellSettings[(indexPath as NSIndexPath).row] + let layout = list!.layouts[(indexPath as NSIndexPath).row] let bubbleCell = (cell as! AABubbleCell) let isShowNewMessages = message.rid == unreadMessageId bubbleCell.performBind(message, receiveDate: receiveDate, readDate: readDate, setting: setting, isShowNewMessages: isShowNewMessages, layout: layout) } - public func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAtIndex section: Int) -> UIEdgeInsets { + open func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAtIndex section: Int) -> UIEdgeInsets { return UIEdgeInsetsMake(6, 0, 100, 0) } - public func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAtIndex section: Int) -> CGFloat { + open func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAtIndex section: Int) -> CGFloat { return 0 } - public func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAtIndex section: Int) -> CGFloat { + open func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAtIndex section: Int) -> CGFloat { return 0 } - public override func collectionView(collectionView: UICollectionView, canPerformAction action: Selector, forItemAtIndexPath indexPath: NSIndexPath, withSender sender: AnyObject!) -> Bool { + open override func collectionView(_ collectionView: UICollectionView, canPerformAction action: Selector, forItemAt indexPath: IndexPath, withSender sender: Any!) -> Bool { return true } - public override func collectionView(collectionView: UICollectionView, shouldShowMenuForItemAtIndexPath indexPath: NSIndexPath) -> Bool { + open override func collectionView(_ collectionView: UICollectionView, shouldShowMenuForItemAt indexPath: IndexPath) -> Bool { return true } - public override func collectionView(collectionView: UICollectionView, performAction action: Selector, forItemAtIndexPath indexPath: NSIndexPath, withSender sender: AnyObject!) { + open override func collectionView(_ collectionView: UICollectionView, performAction action: Selector, forItemAt indexPath: IndexPath, withSender sender: Any!) { } @@ -192,28 +192,28 @@ public class AAConversationContentController: SLKTextViewController, ARDisplayLi return self.displayList.getProcessedList() as? AAPreprocessedList } - public override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + open override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return isStarted ? getCount() : 0 } - public override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell { + open override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = buildCell(collectionView, cellForRowAtIndexPath: indexPath) bindCell(collectionView, cellForRowAtIndexPath: indexPath, cell: cell) - displayList.touchWithIndex(jint(indexPath.row)) + displayList.touch(with: jint((indexPath as NSIndexPath).row)) return cell } - public override func collectionView(collectionView: UICollectionView, didUnhighlightItemAtIndexPath indexPath: NSIndexPath) { - let cell = collectionView.cellForItemAtIndexPath(indexPath) as! AABubbleCell + open override func collectionView(_ collectionView: UICollectionView, didUnhighlightItemAt indexPath: IndexPath) { + let cell = collectionView.cellForItem(at: indexPath) as! AABubbleCell cell.updateView() } - public override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) { - let cell = collectionView.cellForItemAtIndexPath(indexPath) as! AABubbleCell + open override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + let cell = collectionView.cellForItem(at: indexPath) as! AABubbleCell cell.updateView() } - public override func viewDidDisappear(animated: Bool) { + open override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) isVisible = false @@ -233,28 +233,28 @@ public class AAConversationContentController: SLKTextViewController, ARDisplayLi func displayListForController() -> ARBindedDisplayList { let res = Actor.getMessageDisplayList(peer) - if (res.getListProcessor() == nil) { - res.setListProcessor(AAListProcessor(peer: peer)) + if (res?.getProcessor() == nil) { + res?.setListProcessor(AAListProcessor(peer: peer)) } - return res + return res! } - public func objectAtIndexPath(indexPath: NSIndexPath) -> AnyObject? { - return objectAtIndex(indexPath.row) + open func objectAtIndexPath(_ indexPath: IndexPath) -> AnyObject? { + return objectAtIndex((indexPath as NSIndexPath).row) } - public func objectAtIndex(index: Int) -> AnyObject? { - return displayList.itemWithIndex(jint(index)) + open func objectAtIndex(_ index: Int) -> AnyObject? { + return displayList.item(with: jint(index)) as AnyObject? } - public func getCount() -> Int { + open func getCount() -> Int { if (isUpdating) { return self.prevCount } return Int(displayList.size()) } - public func onCollectionChangedWithChanges(modification: ARAppleListUpdate!) { + open func onCollectionChanged(withChanges modification: ARAppleListUpdate!) { // if isUpdating { // pendingUpdates.append(modification) @@ -284,34 +284,34 @@ public class AAConversationContentController: SLKTextViewController, ARDisplayLi // Removed rows if modification.removedCount() > 0 { - var rows = [NSIndexPath]() + var rows = [IndexPath]() for i in 0.. 0 { - var rows = [NSIndexPath]() + var rows = [IndexPath]() for i in 0.. 0 { for i in 0.. \(destRow)") } } @@ -364,25 +364,25 @@ public class AAConversationContentController: SLKTextViewController, ARDisplayLi } } - var forcedRows = [NSIndexPath]() - let visibleIndexes = self.collectionView.indexPathsForVisibleItems() + var forcedRows = [IndexPath]() + let visibleIndexes = self.collectionView.indexPathsForVisibleItems for ind in updated { - let indexPath = NSIndexPath(forRow: ind, inSection: 0) + let indexPath = IndexPath(row: ind, section: 0) if visibleIndexes.contains(indexPath) { - let cell = self.collectionView.cellForItemAtIndexPath(indexPath) + let cell = self.collectionView.cellForItem(at: indexPath) self.bindCell(self.collectionView, cellForRowAtIndexPath: indexPath, cell: cell!) } } for ind in updatedForce { - let indexPath = NSIndexPath(forRow: ind, inSection: 0) + let indexPath = IndexPath(row: ind, section: 0) forcedRows.append(indexPath) } if (forcedRows.count > 0) { self.collectionViewLayout.beginUpdates(false, list: list, unread: unreadMessageId) self.collectionView.performBatchUpdates({ () -> Void in - self.collectionView.reloadItemsAtIndexPaths(forcedRows) + self.collectionView.reloadItems(at: forcedRows) }, completion: nil) } @@ -394,23 +394,23 @@ public class AAConversationContentController: SLKTextViewController, ARDisplayLi } } - private func completeUpdates(modification: ARAppleListUpdate!) { + fileprivate func completeUpdates(_ modification: ARAppleListUpdate!) { } - private func messageStatesUpdated(start: jlong, end: jlong) { - let visibleIndexes = self.collectionView.indexPathsForVisibleItems() + fileprivate func messageStatesUpdated(_ start: jlong, end: jlong) { + let visibleIndexes = self.collectionView.indexPathsForVisibleItems for ind in visibleIndexes { - if let obj = objectAtIndex(ind.row) { + if let obj = objectAtIndex((ind as NSIndexPath).row) { if obj.senderId == Actor.myUid() && obj.sortDate >= start && obj.sortDate <= end { - let cell = self.collectionView.cellForItemAtIndexPath(ind) + let cell = self.collectionView.cellForItem(at: ind) self.bindCell(self.collectionView, cellForRowAtIndexPath: ind, cell: cell!) } } } } - public func willUpdate() { + open func willUpdate() { isLoadedAfter = false if getCount() > 0 && !isLoaded { isLoaded = true @@ -435,16 +435,16 @@ public class AAConversationContentController: SLKTextViewController, ARDisplayLi } } - public func didUpdate() { + open func didUpdate() { if isLoadedAfter { if unreadIndex != nil { - self.collectionView.scrollToItemAtIndexPath(NSIndexPath(forItem: unreadIndex!, inSection: 0), atScrollPosition: UICollectionViewScrollPosition.Bottom, animated: false) + self.collectionView.scrollToItem(at: IndexPath(item: unreadIndex!, section: 0), at: UICollectionViewScrollPosition.bottom, animated: false) unreadIndex = nil } } } - public func onBubbleAvatarTap(view: UIView, uid: jint) { + open func onBubbleAvatarTap(_ view: UIView, uid: jint) { var controller: AAViewController! = ActorSDK.sharedActor().delegate.actorControllerForUser(Int(uid)) if controller == nil { controller = AAUserViewController(uid: Int(uid)) @@ -454,16 +454,16 @@ public class AAConversationContentController: SLKTextViewController, ARDisplayLi navigation.viewControllers = [controller] let popover = UIPopoverController(contentViewController: navigation) controller.popover = popover - popover.presentPopoverFromRect(view.bounds, inView: view, permittedArrowDirections: UIPopoverArrowDirection.Any, animated: true) + popover.present(from: view.bounds, in: view, permittedArrowDirections: UIPopoverArrowDirection.any, animated: true) } else { navigateNext(controller, removeCurrent: false) } } - public override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) { - super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator) + open override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { + super.viewWillTransition(to: size, with: coordinator) - dispatch_async(dispatch_get_main_queue(), { () -> Void in + DispatchQueue.main.async(execute: { () -> Void in // self.collectionView.collectionViewLayout.invalidateLayout() self.collectionView.performBatchUpdates(nil, completion: nil) }) @@ -474,7 +474,7 @@ public class AAConversationContentController: SLKTextViewController, ARDisplayLi /////////////////////// - func playVoiceFromPath(path:String,fileId:jlong,position:Float) { + func playVoiceFromPath(_ path:String,fileId:jlong,position:Float) { if (self.currentAudioFileId != fileId) { @@ -513,15 +513,15 @@ public class AAConversationContentController: SLKTextViewController, ARDisplayLi } - func playVideoFromPath(path:String) { + func playVideoFromPath(_ path:String) { - let player = AVPlayer(URL: NSURL(fileURLWithPath: path)) + let player = AVPlayer(url: URL(fileURLWithPath: path)) let playerController = AVPlayerViewController() playerController.player = player - self.presentViewController(playerController, animated: true) { + self.present(playerController, animated: true) { player.play() } } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/AANavigationBadge.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/AANavigationBadge.swift index 751b164d47..f748dbadd3 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/AANavigationBadge.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/AANavigationBadge.swift @@ -6,38 +6,38 @@ import Foundation class AANavigationBadge { - private static var binder = AABinder() - private static let badgeView = UIImageView() - private static var badgeCount = 0 - private static var isBadgeVisible = false + fileprivate static var binder = AABinder() + fileprivate static let badgeView = UIImageView() + fileprivate static var badgeCount = 0 + fileprivate static var isBadgeVisible = false - private static var isInited = false + fileprivate static var isInited = false - class private func start() { + class fileprivate func start() { if isInited { return } isInited = true - badgeView.image = Imaging.roundedImage(UIColor(rgb: 0xfe0000), size: CGSizeMake(16, 16), radius: 8) + badgeView.image = Imaging.roundedImage(UIColor(rgb: 0xfe0000), size: CGSize(width: 16, height: 16), radius: 8) // badgeView.frame = CGRectMake(16, 22, 32, 16) badgeView.alpha = 0 let badgeText = UILabel() badgeText.text = "0" - badgeText.textColor = UIColor.whiteColor() + badgeText.textColor = UIColor.white // badgeText.frame = CGRectMake(0, 0, 32, 16) - badgeText.font = UIFont.systemFontOfSize(12) - badgeText.textAlignment = NSTextAlignment.Center + badgeText.font = UIFont.systemFont(ofSize: 12) + badgeText.textAlignment = NSTextAlignment.center badgeView.addSubview(badgeText) - UIApplication.sharedApplication().windows.first!.addSubview(badgeView) + UIApplication.shared.windows.first!.addSubview(badgeView) // Bind badge counter binder.bind(Actor.getGlobalState().globalCounter, closure: { (value: JavaLangInteger?) -> () in if let v = value { - self.badgeCount = Int(v.integerValue) + self.badgeCount = Int(v.intValue) } else { self.badgeCount = 0 } @@ -49,13 +49,13 @@ class AANavigationBadge { self.badgeView.hideView() } - badgeText.frame = CGRectMake(0, 0, 128, 16) + badgeText.frame = CGRect(x: 0, y: 0, width: 128, height: 16) badgeText.sizeToFit() if badgeText.frame.width < 8 { - self.badgeView.frame = CGRectMake(16, 22, 16, 16) + self.badgeView.frame = CGRect(x: 16, y: 22, width: 16, height: 16) } else { - self.badgeView.frame = CGRectMake(16, 22, badgeText.frame.width + 8, 16) + self.badgeView.frame = CGRect(x: 16, y: 22, width: badgeText.frame.width + 8, height: 16) } badgeText.frame = self.badgeView.bounds }) @@ -80,4 +80,4 @@ class AANavigationBadge { isBadgeVisible = false self.badgeView.hideView() } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Cell/AABubbleBaseFileCell.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Cell/AABubbleBaseFileCell.swift index e8edb7ed1b..1370ba3d2e 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Cell/AABubbleBaseFileCell.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Cell/AABubbleBaseFileCell.swift @@ -4,17 +4,17 @@ import Foundation -public class AABubbleBaseFileCell: AABubbleCell { +open class AABubbleBaseFileCell: AABubbleCell { - private var bindGeneration = 0; + fileprivate var bindGeneration = 0; - private var bindedDownloadFile: jlong? = nil - private var bindedDownloadCallback: AAFileCallback? = nil + fileprivate var bindedDownloadFile: jlong? = nil + fileprivate var bindedDownloadCallback: AAFileCallback? = nil - private var bindedUploadFile: jlong? = nil - private var bindedUploadCallback: AAUploadFileCallback? = nil + fileprivate var bindedUploadFile: jlong? = nil + fileprivate var bindedUploadCallback: AAUploadFileCallback? = nil - public func fileBind(message: ACMessage, autoDownload: Bool) { + open func fileBind(_ message: ACMessage, autoDownload: Bool) { if let doc = message.content as? ACDocumentContent { let selfGeneration = prepareBind() @@ -22,7 +22,7 @@ public class AABubbleBaseFileCell: AABubbleCell { if let source = doc.getSource() as? ACFileRemoteSource { let fileReference = source.getFileReference(); - bindedDownloadFile = fileReference.getFileId() + bindedDownloadFile = fileReference?.getFileId() bindedDownloadCallback = AAFileCallback(notDownloaded: { () -> () in if (self.bindGeneration != selfGeneration) { return @@ -40,7 +40,7 @@ public class AABubbleBaseFileCell: AABubbleCell { self.fileStateChanged(reference, progress: nil, isPaused: false, isUploading: false, selfGeneration: selfGeneration) }) - Actor.bindRawFileWithReference(fileReference, autoStart: autoDownload, withCallback: bindedDownloadCallback) + Actor.bindRawFile(with: fileReference, autoStart: autoDownload, with: bindedDownloadCallback) } else if let source = doc.getSource() as? ACFileLocalSource { let fileReference = source.getFileDescriptor(); @@ -62,7 +62,7 @@ public class AABubbleBaseFileCell: AABubbleCell { self.fileStateChanged(fileReference, progress: nil, isPaused: false, isUploading: false, selfGeneration: selfGeneration) }); - Actor.bindRawUploadFileWithRid(message.rid, withCallback: bindedUploadCallback) + Actor.bindRawUploadFile(withRid: message.rid, with: bindedUploadCallback) } else { fatalError("Unsupported file source") } @@ -72,7 +72,7 @@ public class AABubbleBaseFileCell: AABubbleCell { let selfGeneration = prepareBind() - bindedDownloadFile = file.reference.getFileId() + bindedDownloadFile = file?.reference.getFileId() bindedDownloadCallback = AAFileCallback(notDownloaded: { () -> () in if (self.bindGeneration != selfGeneration) { return @@ -90,13 +90,13 @@ public class AABubbleBaseFileCell: AABubbleCell { self.fileStateChanged(reference, progress: nil, isPaused: false, isUploading: false, selfGeneration: selfGeneration) }) - Actor.bindRawFileWithReference(ACFileReference(ARApiFileLocation: file.reference.getFileLocation(), withNSString: file.reference.fileName, withInt: file.reference.fileSize), autoStart: autoDownload, withCallback: bindedDownloadCallback) + Actor.bindRawFile(with: ACFileReference(arApiFileLocation: file?.reference.getFileLocation(), with: file?.reference.fileName, with: (file?.reference.fileSize)!), autoStart: autoDownload, with: bindedDownloadCallback) } else { fatalError("Unsupported message type") } } - public func bindFile(fileReference: ACFileReference, autoDownload: Bool) { + open func bindFile(_ fileReference: ACFileReference, autoDownload: Bool) { let selfGeneration = prepareBind() @@ -118,10 +118,10 @@ public class AABubbleBaseFileCell: AABubbleCell { self.fileStateChanged(reference, progress: nil, isPaused: false, isUploading: false, selfGeneration: selfGeneration) }) - Actor.bindRawFileWithReference(fileReference, autoStart: autoDownload, withCallback: bindedDownloadCallback) + Actor.bindRawFile(with: fileReference, autoStart: autoDownload, with: bindedDownloadCallback) } - private func prepareBind() -> Int { + fileprivate func prepareBind() -> Int { // Next generation of binding bindGeneration += 1 @@ -134,11 +134,11 @@ public class AABubbleBaseFileCell: AABubbleCell { return selfGeneration } - public func fileStateChanged(reference: String?, progress: Int?, isPaused: Bool, isUploading: Bool, selfGeneration: Int) { + open func fileStateChanged(_ reference: String?, progress: Int?, isPaused: Bool, isUploading: Bool, selfGeneration: Int) { } - public func runOnUiThread(selfGeneration: Int, closure: (()->())?) -> Bool { + open func runOnUiThread(_ selfGeneration: Int, closure: (()->())?) -> Bool { if (selfGeneration != self.bindGeneration) { return false } @@ -157,17 +157,17 @@ public class AABubbleBaseFileCell: AABubbleCell { return res } - public func fileUnbind() { + open func fileUnbind() { if (bindedDownloadFile != nil && bindedDownloadCallback != nil) { - Actor.unbindRawFileWithFileId(bindedDownloadFile!, autoCancel: false, withCallback: bindedDownloadCallback) + Actor.unbindRawFile(withFileId: bindedDownloadFile!, autoCancel: false, with: bindedDownloadCallback) bindedDownloadFile = nil bindedDownloadCallback = nil } if (bindedUploadFile != nil && bindedUploadCallback != nil) { - Actor.unbindRawUploadFileWithRid(bindedUploadFile!, withCallback: bindedUploadCallback) + Actor.unbindRawUploadFile(withRid: bindedUploadFile!, with: bindedUploadCallback) bindedUploadFile = nil bindedUploadCallback = nil } } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Cell/AABubbleCell.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Cell/AABubbleCell.swift index ee88447ff4..277ff596d3 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Cell/AABubbleCell.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Cell/AABubbleCell.swift @@ -10,17 +10,17 @@ import UIKit */ public enum BubbleType { // Outcome text bubble - case TextOut + case textOut // Income text bubble - case TextIn + case textIn // Outcome media bubble - case MediaOut + case mediaOut // Income media bubble - case MediaIn + case mediaIn // Service bubble - case Service + case service // Sticker bubble - case Sticker + case sticker } /** @@ -28,9 +28,9 @@ public enum BubbleType { */ public protocol AABubbleLayouter { - func isSuitable(message: ACMessage) -> Bool + func isSuitable(_ message: ACMessage) -> Bool - func buildLayout(peer: ACPeer, message: ACMessage) -> AACellLayout + func buildLayout(_ peer: ACPeer, message: ACMessage) -> AACellLayout func cellClass() -> AnyClass } @@ -44,52 +44,52 @@ extension AABubbleLayouter { /** Root class for bubble cells */ -public class AABubbleCell: UICollectionViewCell { - - public static let bubbleContentTop: CGFloat = 6 - public static let bubbleContentBottom: CGFloat = 6 - public static let bubbleTop: CGFloat = 3 - public static let bubbleTopCompact: CGFloat = 1 - public static let bubbleBottom: CGFloat = 3 - public static let bubbleBottomCompact: CGFloat = 1 - public static let avatarPadding: CGFloat = 39 - public static let dateSize: CGFloat = 30 - public static let newMessageSize: CGFloat = 30 +open class AABubbleCell: UICollectionViewCell { + + open static let bubbleContentTop: CGFloat = 6 + open static let bubbleContentBottom: CGFloat = 6 + open static let bubbleTop: CGFloat = 3 + open static let bubbleTopCompact: CGFloat = 1 + open static let bubbleBottom: CGFloat = 3 + open static let bubbleBottomCompact: CGFloat = 1 + open static let avatarPadding: CGFloat = 39 + open static let dateSize: CGFloat = 30 + open static let newMessageSize: CGFloat = 30 // // Cached text bubble images // - private static var cachedOutTextBg = UIImage.tinted("BubbleOutgoingFull", color: ActorSDK.sharedActor().style.chatTextBubbleOutColor) + fileprivate static var cachedOutTextBg = UIImage.tinted("BubbleOutgoingFull", color: ActorSDK.sharedActor().style.chatTextBubbleOutColor) - private static var cachedOutTextBgShadow = ActorSDK.sharedActor().style.bubbleShadowEnabled ? UIImage.tinted("BubbleOutgoingFull", color: ActorSDK.sharedActor().style.chatTextBubbleShadowColor) : UIImage() + fileprivate static var cachedOutTextBgShadow = ActorSDK.sharedActor().style.bubbleShadowEnabled ? UIImage.tinted("BubbleOutgoingFull", color: ActorSDK.sharedActor().style.chatTextBubbleShadowColor) : UIImage() - private static var cachedOutTextBgBorder = UIImage.tinted("BubbleOutgoingFullBorder", color: ActorSDK.sharedActor().style.chatTextBubbleOutBorderColor) - private static var cachedOutTextCompactBg = UIImage.tinted("BubbleOutgoingPartial", color: ActorSDK.sharedActor().style.chatTextBubbleOutColor) - private static var cachedOutTextCompactBgShadow = ActorSDK.sharedActor().style.bubbleShadowEnabled ? UIImage.tinted("BubbleOutgoingPartial", color: ActorSDK.sharedActor().style.chatTextBubbleShadowColor) : UIImage() - private static var cachedOutTextCompactSelectedBg = UIImage.tinted("BubbleOutgoingPartial", color: ActorSDK.sharedActor().style.chatTextBubbleOutSelectedColor) - private static var cachedOutTextCompactBgBorder = UIImage.tinted("BubbleOutgoingPartialBorder", color: ActorSDK.sharedActor().style.chatTextBubbleOutBorderColor) + fileprivate static var cachedOutTextBgBorder = UIImage.tinted("BubbleOutgoingFullBorder", color: ActorSDK.sharedActor().style.chatTextBubbleOutBorderColor) + fileprivate static var cachedOutTextCompactBg = UIImage.tinted("BubbleOutgoingPartial", color: ActorSDK.sharedActor().style.chatTextBubbleOutColor) + fileprivate static var cachedOutTextCompactBgShadow = ActorSDK.sharedActor().style.bubbleShadowEnabled ? UIImage.tinted("BubbleOutgoingPartial", color: ActorSDK.sharedActor().style.chatTextBubbleShadowColor) : UIImage() + fileprivate static var cachedOutTextCompactSelectedBg = UIImage.tinted("BubbleOutgoingPartial", color: ActorSDK.sharedActor().style.chatTextBubbleOutSelectedColor) + fileprivate static var cachedOutTextCompactBgBorder = UIImage.tinted("BubbleOutgoingPartialBorder", color: ActorSDK.sharedActor().style.chatTextBubbleOutBorderColor) - private static var cachedInTextBg = UIImage.tinted("BubbleIncomingFull", color: ActorSDK.sharedActor().style.chatTextBubbleInColor) - private static var cachedInTextBgShadow = ActorSDK.sharedActor().style.bubbleShadowEnabled ? UIImage.tinted("BubbleIncomingFull", color: ActorSDK.sharedActor().style.chatTextBubbleShadowColor) : UIImage() - private static var cachedInTextBgBorder = UIImage.tinted("BubbleIncomingFullBorder", color: ActorSDK.sharedActor().style.chatTextBubbleInBorderColor) - private static var cachedInTextCompactBg = UIImage.tinted("BubbleIncomingPartial", color: ActorSDK.sharedActor().style.chatTextBubbleInColor) - private static var cachedInTextCompactBgShadow = ActorSDK.sharedActor().style.bubbleShadowEnabled ?UIImage.tinted("BubbleIncomingPartial", color: ActorSDK.sharedActor().style.chatTextBubbleShadowColor) : UIImage() - private static var cachedInTextCompactSelectedBg = UIImage.tinted("BubbleIncomingPartial", color: ActorSDK.sharedActor().style.chatTextBubbleInSelectedColor) - private static var cachedInTextCompactBgBorder = UIImage.tinted("BubbleIncomingPartialBorder", color: ActorSDK.sharedActor().style.chatTextBubbleInBorderColor) + fileprivate static var cachedInTextBg = UIImage.tinted("BubbleIncomingFull", color: ActorSDK.sharedActor().style.chatTextBubbleInColor) + fileprivate static var cachedInTextBgShadow = ActorSDK.sharedActor().style.bubbleShadowEnabled ? UIImage.tinted("BubbleIncomingFull", color: ActorSDK.sharedActor().style.chatTextBubbleShadowColor) : UIImage() + fileprivate static var cachedInTextBgBorder = UIImage.tinted("BubbleIncomingFullBorder", color: ActorSDK.sharedActor().style.chatTextBubbleInBorderColor) + fileprivate static var cachedInTextCompactBg = UIImage.tinted("BubbleIncomingPartial", color: ActorSDK.sharedActor().style.chatTextBubbleInColor) + fileprivate static var cachedInTextCompactBgShadow = ActorSDK.sharedActor().style.bubbleShadowEnabled ?UIImage.tinted("BubbleIncomingPartial", color: ActorSDK.sharedActor().style.chatTextBubbleShadowColor) : UIImage() + fileprivate static var cachedInTextCompactSelectedBg = UIImage.tinted("BubbleIncomingPartial", color: ActorSDK.sharedActor().style.chatTextBubbleInSelectedColor) + fileprivate static var cachedInTextCompactBgBorder = UIImage.tinted("BubbleIncomingPartialBorder", color: ActorSDK.sharedActor().style.chatTextBubbleInBorderColor) // // Cached media bubble images // - private static let cachedMediaBg = UIImage.tinted("BubbleOutgoingPartial", color: ActorSDK.sharedActor().style.chatMediaBubbleColor) - private static var cachedMediaBgBorder = UIImage.tinted("BubbleOutgoingPartialBorder", color: ActorSDK.sharedActor().style.chatMediaBubbleBorderColor) + fileprivate static let cachedMediaBg = UIImage.tinted("BubbleOutgoingPartial", color: ActorSDK.sharedActor().style.chatMediaBubbleColor) + fileprivate static var cachedMediaBgBorder = UIImage.tinted("BubbleOutgoingPartialBorder", color: ActorSDK.sharedActor().style.chatMediaBubbleBorderColor) // // Cached Service bubble images // - private static var cachedServiceBg:UIImage = Imaging.roundedImage(ActorSDK.sharedActor().style.chatServiceBubbleColor, size: CGSizeMake(18, 18), radius: 9) + fileprivate static var cachedServiceBg:UIImage = Imaging.roundedImage(ActorSDK.sharedActor().style.chatServiceBubbleColor, size: CGSize(width: 18, height: 18), radius: 9) // // Cached Date bubble images @@ -97,28 +97,28 @@ public class AABubbleCell: UICollectionViewCell { // private static var dateBgImage = Imaging.roundedImage(ActorSDK.sharedActor().style.chatDateBubbleColor, size: CGSizeMake(18, 18), radius: 9) - private static var dateBgImage = ActorSDK.sharedActor().style.statusBackgroundImage + fileprivate static var dateBgImage = ActorSDK.sharedActor().style.statusBackgroundImage // MARK: - // MARK: Public vars // Views - public let avatarView = AAAvatarView() - public var avatarAdded: Bool = false + open let avatarView = AAAvatarView() + open var avatarAdded: Bool = false - public let bubble = UIImageView() - public let bubbleShadow = UIImageView() - public let bubbleBorder = UIImageView() + open let bubble = UIImageView() + open let bubbleShadow = UIImageView() + open let bubbleBorder = UIImageView() - private let dateText = UILabel() - private let dateBg = UIImageView() + fileprivate let dateText = UILabel() + fileprivate let dateBg = UIImageView() - private let newMessage = UILabel() + fileprivate let newMessage = UILabel() // Layout - public var contentInsets : UIEdgeInsets = UIEdgeInsets() - public var bubbleInsets : UIEdgeInsets = UIEdgeInsets() - public var fullContentInsets : UIEdgeInsets { + open var contentInsets : UIEdgeInsets = UIEdgeInsets() + open var bubbleInsets : UIEdgeInsets = UIEdgeInsets() + open var fullContentInsets : UIEdgeInsets { get { return UIEdgeInsets( top: contentInsets.top + bubbleInsets.top + (isShowDate ? AABubbleCell.dateSize : 0) + (isShowNewMessages ? AABubbleCell.newMessageSize : 0), @@ -127,26 +127,26 @@ public class AABubbleCell: UICollectionViewCell { right: contentInsets.right + bubbleInsets.right) } } - public var needLayout: Bool = true + open var needLayout: Bool = true - public let groupContentInsetY = 20.0 - public let groupContentInsetX = 40.0 - public var bubbleVerticalSpacing: CGFloat = 6.0 - public let bubblePadding: CGFloat = 6; - public let bubbleMediaPadding: CGFloat = 10; + open let groupContentInsetY = 20.0 + open let groupContentInsetX = 40.0 + open var bubbleVerticalSpacing: CGFloat = 6.0 + open let bubblePadding: CGFloat = 6; + open let bubbleMediaPadding: CGFloat = 10; // Binded data - public var peer: ACPeer! - public weak var controller: AAConversationContentController! - public var isGroup: Bool = false - public var isFullSize: Bool! - public var bindedSetting: AACellSetting? - - public var bindedMessage: ACMessage? = nil - public var bubbleType:BubbleType? = nil - public var isOut: Bool = false - public var isShowDate: Bool = false - public var isShowNewMessages: Bool = false + open var peer: ACPeer! + open weak var controller: AAConversationContentController! + open var isGroup: Bool = false + open var isFullSize: Bool! + open var bindedSetting: AACellSetting? + + open var bindedMessage: ACMessage? = nil + open var bubbleType:BubbleType? = nil + open var isOut: Bool = false + open var isShowDate: Bool = false + open var isShowNewMessages: Bool = false var appStyle: ActorStyle { get { @@ -165,19 +165,19 @@ public class AABubbleCell: UICollectionViewCell { dateBg.image = AABubbleCell.dateBgImage dateText.font = UIFont.mediumSystemFontOfSize(12) dateText.textColor = appStyle.chatDateTextColor - dateText.contentMode = UIViewContentMode.Center - dateText.textAlignment = NSTextAlignment.Center + dateText.contentMode = UIViewContentMode.center + dateText.textAlignment = NSTextAlignment.center newMessage.font = UIFont.mediumSystemFontOfSize(14) newMessage.textColor = appStyle.chatUnreadTextColor - newMessage.contentMode = UIViewContentMode.Center - newMessage.textAlignment = NSTextAlignment.Center + newMessage.contentMode = UIViewContentMode.center + newMessage.textAlignment = NSTextAlignment.center newMessage.backgroundColor = appStyle.chatUnreadBgColor newMessage.text = AALocalized("ChatNewMessages") //"New Messages" - contentView.transform = CGAffineTransformMake(1, 0, 0, -1, 0, 0) + contentView.transform = CGAffineTransform(a: 1, b: 0, c: 0, d: -1, tx: 0, ty: 0) if appStyle.bubbleShadowEnabled { contentView.addSubview(bubbleShadow) @@ -190,9 +190,9 @@ public class AABubbleCell: UICollectionViewCell { avatarView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(AABubbleCell.avatarDidTap))) - avatarView.userInteractionEnabled = true + avatarView.isUserInteractionEnabled = true - backgroundColor = UIColor.clearColor() + backgroundColor = UIColor.clear // Speed up animations self.layer.speed = 1.5 @@ -207,7 +207,7 @@ public class AABubbleCell: UICollectionViewCell { fatalError("init(coder:) has not been implemented") } - func setConfig(peer: ACPeer, controller: AAConversationContentController) { + func setConfig(_ peer: ACPeer, controller: AAConversationContentController) { self.peer = peer self.controller = controller if (peer.isGroup && !isFullSize) { @@ -215,22 +215,22 @@ public class AABubbleCell: UICollectionViewCell { } } - public override func canBecomeFirstResponder() -> Bool { + open override var canBecomeFirstResponder : Bool { return false } - public override func canPerformAction(action: Selector, withSender sender: AnyObject?) -> Bool { - if action == #selector(NSObject.delete(_:)) { - return true - } - return false - } - - public override func delete(sender: AnyObject?) { - let rids = IOSLongArray(length: 1) - rids.replaceLongAtIndex(0, withLong: bindedMessage!.rid) - Actor.deleteMessagesWithPeer(self.peer, withRids: rids) - } +// open override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool { +// if action == #selector(Object.delete(_:)) { +// return true +// } +// return false +// } + +// open override func delete(_ sender: Any?) { +// let rids = IOSLongArray(length: 1) +// rids?.replaceLong(at: 0, withLong: bindedMessage!.rid) +// Actor.deleteMessages(with: self.peer, withRids: rids) +// } func avatarDidTap() { if bindedMessage != nil { @@ -238,7 +238,7 @@ public class AABubbleCell: UICollectionViewCell { } } - public func performBind(message: ACMessage, receiveDate: jlong, readDate: jlong, setting: AACellSetting, isShowNewMessages: Bool, layout: AACellLayout) { + open func performBind(_ message: ACMessage, receiveDate: jlong, readDate: jlong, setting: AACellSetting, isShowNewMessages: Bool, layout: AACellLayout) { var reuse = false if (bindedMessage != nil && bindedMessage?.rid == message.rid) { @@ -257,11 +257,11 @@ public class AABubbleCell: UICollectionViewCell { let group = Actor.getGroupWithGid(self.peer.peerId) let avatar: ACAvatar? = group.getAvatarModel().get() let name = group.getNameModel().get() - avatarView.bind(name, id: Int(user.getId()), avatar: avatar) + avatarView.bind(name!, id: Int(user.getId()), avatar: avatar) } else { let avatar: ACAvatar? = user.getAvatarModel().get() let name = user.getNameModel().get() - avatarView.bind(name, id: Int(user.getId()), avatar: avatar) + avatarView.bind(name!, id: Int(user.getId()), avatar: avatar) } if !avatarAdded { contentView.addSubview(avatarView) @@ -290,16 +290,16 @@ public class AABubbleCell: UICollectionViewCell { } } - public func bind(message: ACMessage, receiveDate: jlong, readDate: jlong, reuse: Bool, cellLayout: AACellLayout, setting: AACellSetting) { + open func bind(_ message: ACMessage, receiveDate: jlong, readDate: jlong, reuse: Bool, cellLayout: AACellLayout, setting: AACellSetting) { fatalError("bind(message:) has not been implemented") } - public func bindBubbleType(type: BubbleType, isCompact: Bool) { + open func bindBubbleType(_ type: BubbleType, isCompact: Bool) { self.bubbleType = type // Update Bubble background images switch(type) { - case BubbleType.TextIn: + case BubbleType.textIn: if (isCompact) { bubble.image = AABubbleCell.cachedInTextCompactBg bubbleBorder.image = AABubbleCell.cachedInTextCompactBgBorder @@ -310,7 +310,7 @@ public class AABubbleCell: UICollectionViewCell { bubbleShadow.image = AABubbleCell.cachedInTextBgShadow } break - case BubbleType.TextOut: + case BubbleType.textOut: if (isCompact) { bubble.image = AABubbleCell.cachedOutTextCompactBg bubbleBorder.image = AABubbleCell.cachedOutTextCompactBgBorder @@ -321,22 +321,22 @@ public class AABubbleCell: UICollectionViewCell { bubbleShadow.image = AABubbleCell.cachedOutTextBgShadow } break - case BubbleType.MediaIn: + case BubbleType.mediaIn: bubble.image = AABubbleCell.cachedMediaBg bubbleBorder.image = AABubbleCell.cachedMediaBgBorder bubbleShadow.image = nil break - case BubbleType.MediaOut: + case BubbleType.mediaOut: bubble.image = AABubbleCell.cachedMediaBg bubbleBorder.image = AABubbleCell.cachedMediaBgBorder bubbleShadow.image = nil break - case BubbleType.Service: + case BubbleType.service: bubble.image = AABubbleCell.cachedServiceBg bubbleBorder.image = nil bubbleShadow.image = nil break - case BubbleType.Sticker: + case BubbleType.sticker: bubble.image = nil; bubbleBorder.image = nil bubbleShadow.image = nil @@ -347,7 +347,7 @@ public class AABubbleCell: UICollectionViewCell { func updateView() { let type = self.bubbleType! as BubbleType switch (type) { - case BubbleType.TextIn: + case BubbleType.textIn: if (!isFullSize!) { bubble.image = AABubbleCell.cachedInTextCompactBg bubbleBorder.image = AABubbleCell.cachedInTextCompactBgBorder @@ -358,7 +358,7 @@ public class AABubbleCell: UICollectionViewCell { bubbleShadow.image = AABubbleCell.cachedInTextBgShadow } break - case BubbleType.TextOut: + case BubbleType.textOut: if (!isFullSize!) { bubble.image = AABubbleCell.cachedOutTextCompactBg bubbleBorder.image = AABubbleCell.cachedOutTextCompactBgBorder @@ -369,22 +369,22 @@ public class AABubbleCell: UICollectionViewCell { bubbleShadow.image = AABubbleCell.cachedOutTextBgShadow } break - case BubbleType.MediaIn: + case BubbleType.mediaIn: bubble.image = AABubbleCell.cachedMediaBg bubbleBorder.image = AABubbleCell.cachedMediaBgBorder bubbleShadow.image = nil break - case BubbleType.MediaOut: + case BubbleType.mediaOut: bubble.image = AABubbleCell.cachedMediaBg bubbleBorder.image = AABubbleCell.cachedMediaBgBorder bubbleShadow.image = nil break - case BubbleType.Service: + case BubbleType.service: bubble.image = AABubbleCell.cachedServiceBg bubbleBorder.image = nil bubbleShadow.image = nil break - case BubbleType.Sticker: + case BubbleType.sticker: bubble.image = nil; bubbleBorder.image = nil bubbleShadow.image = nil @@ -395,7 +395,7 @@ public class AABubbleCell: UICollectionViewCell { // MARK: - // MARK: Layout - public override func layoutSubviews() { + open override func layoutSubviews() { super.layoutSubviews() UIView.performWithoutAnimation { () -> Void in @@ -412,17 +412,17 @@ public class AABubbleCell: UICollectionViewCell { func layoutAnchor() { if (isShowDate) { - dateText.frame = CGRectMake(0, 0, 1000, 1000) + dateText.frame = CGRect(x: 0, y: 0, width: 1000, height: 1000) dateText.sizeToFit() - dateText.frame = CGRectMake( - (self.contentView.frame.size.width-dateText.frame.width)/2, 8, dateText.frame.width, 18) - dateBg.frame = CGRectMake(dateText.frame.minX - 8, dateText.frame.minY, dateText.frame.width + 16, 18) + dateText.frame = CGRect( + x: (self.contentView.frame.size.width-dateText.frame.width)/2, y: 8, width: dateText.frame.width, height: 18) + dateBg.frame = CGRect(x: dateText.frame.minX - 8, y: dateText.frame.minY, width: dateText.frame.width + 16, height: 18) - dateText.hidden = false - dateBg.hidden = false + dateText.isHidden = false + dateBg.isHidden = false } else { - dateText.hidden = true - dateBg.hidden = true + dateText.isHidden = true + dateBg.isHidden = true } if (isShowNewMessages) { @@ -430,14 +430,14 @@ public class AABubbleCell: UICollectionViewCell { if (isShowDate) { top += AABubbleCell.dateSize } - newMessage.hidden = false - newMessage.frame = CGRectMake(0, top + CGFloat(2), self.contentView.frame.width, AABubbleCell.newMessageSize - CGFloat(4)) + newMessage.isHidden = false + newMessage.frame = CGRect(x: 0, y: top + CGFloat(2), width: self.contentView.frame.width, height: AABubbleCell.newMessageSize - CGFloat(4)) } else { - newMessage.hidden = true + newMessage.isHidden = true } } - public func layoutContent(maxWidth: CGFloat, offsetX: CGFloat) { + open func layoutContent(_ maxWidth: CGFloat, offsetX: CGFloat) { } @@ -448,7 +448,7 @@ public class AABubbleCell: UICollectionViewCell { // Need to be called in child cells - public func layoutBubble(contentWidth: CGFloat, contentHeight: CGFloat) { + open func layoutBubble(_ contentWidth: CGFloat, contentHeight: CGFloat) { let fullWidth = contentView.bounds.width let bubbleW = contentWidth + contentInsets.left + contentInsets.right let bubbleH = contentHeight + contentInsets.top + contentInsets.bottom @@ -491,13 +491,13 @@ public class AABubbleCell: UICollectionViewCell { height: bubbleH) } - public func layoutBubble(frame: CGRect) { + open func layoutBubble(_ frame: CGRect) { bubble.frame = frame bubbleBorder.frame = frame bubbleShadow.frame = frame } - public override func preferredLayoutAttributesFittingAttributes(layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes { + open override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes { return layoutAttributes } } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Cell/AABubbleContactCell.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Cell/AABubbleContactCell.swift index e9fd6f2eef..9dae467cd3 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Cell/AABubbleContactCell.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Cell/AABubbleContactCell.swift @@ -6,20 +6,20 @@ import Foundation import AddressBookUI import MessageUI -public class AABubbleContactCell: AABubbleCell, ABNewPersonViewControllerDelegate, MFMailComposeViewControllerDelegate, UINavigationControllerDelegate { +open class AABubbleContactCell: AABubbleCell, ABNewPersonViewControllerDelegate, MFMailComposeViewControllerDelegate, UINavigationControllerDelegate { - private let avatar = AAAvatarView() - private let name = UILabel() - private let contact = UILabel() - private var bindedRecords = [String]() - private let tapView = UIView() + fileprivate let avatar = AAAvatarView() + fileprivate let name = UILabel() + fileprivate let contact = UILabel() + fileprivate var bindedRecords = [String]() + fileprivate let tapView = UIView() public init(frame: CGRect) { super.init(frame: frame, isFullSize: false) name.font = UIFont.mediumSystemFontOfSize(17) - contact.font = UIFont.systemFontOfSize(15) - tapView.backgroundColor = UIColor.clearColor() + contact.font = UIFont.systemFont(ofSize: 15) + tapView.backgroundColor = UIColor.clear contentView.addSubview(avatar) contentView.addSubview(name) @@ -29,7 +29,7 @@ public class AABubbleContactCell: AABubbleCell, ABNewPersonViewControllerDelegat contentInsets = UIEdgeInsets(top: 1, left: 1, bottom: 1, right: 1) tapView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(AABubbleContactCell.contactDidTap))) - tapView.userInteractionEnabled = true + tapView.isUserInteractionEnabled = true } public required init(coder aDecoder: NSCoder) { @@ -41,11 +41,11 @@ public class AABubbleContactCell: AABubbleCell, ABNewPersonViewControllerDelegat if let c = m.content as? ACContactContent { let menuBuilder = AAMenuBuilder() let phones = c.getPhones() - for i in 0.. () in - if let url = NSURL(string: "tel:\(p)") { - if !UIApplication.sharedApplication().openURL(url) { + if let url = URL(string: "tel:\(p)") { + if !UIApplication.shared.openURL(url) { self.controller.alertUser("ErrorUnableToCall") } } else { @@ -54,48 +54,48 @@ public class AABubbleContactCell: AABubbleCell, ABNewPersonViewControllerDelegat }) } let emails = c.getEmails() - for i in 0.. () in let emailController = MFMailComposeViewController() emailController.delegate = self emailController.setToRecipients([e]) - self.controller.presentViewController(emailController, animated: true, completion: nil) + self.controller.present(emailController, animated: true, completion: nil) }) } menuBuilder.add(AALocalized("ProfileAddToContacts"), closure: { () -> () in let add = ABNewPersonViewController() add.newPersonViewDelegate = self - let person: ABRecordRef = ABPersonCreate().takeRetainedValue() + let person: ABRecord = ABPersonCreate().takeRetainedValue() let name = c.getName().trim() - let nameParts = name.componentsSeparatedByString(" ") - ABRecordSetValue(person, kABPersonFirstNameProperty, nameParts[0], nil) + let nameParts = name.components(separatedBy: " ") + ABRecordSetValue(person, kABPersonFirstNameProperty, nameParts[0] as CFTypeRef!, nil) if (nameParts.count >= 2) { - let lastName = name.substringFromIndex(nameParts[0].endIndex).trim() - ABRecordSetValue(person, kABPersonLastNameProperty, lastName, nil) + let lastName = name.substring(from: nameParts[0].endIndex).trim() + ABRecordSetValue(person, kABPersonLastNameProperty, lastName as CFTypeRef!, nil) } - if (phones.size() > 0) { - let phonesValues: ABMultiValueRef = ABMultiValueCreateMutable(UInt32(kABMultiStringPropertyType)).takeRetainedValue() - for i in 0.. 0) { + let phonesValues: ABMultiValue = ABMultiValueCreateMutable(UInt32(kABMultiStringPropertyType)).takeRetainedValue() + for i in 0.. 0) { - let phonesValues: ABMultiValueRef = ABMultiValueCreateMutable(UInt32(kABMultiStringPropertyType)).takeRetainedValue() - for i in 0.. 0) { + let phonesValues: ABMultiValue = ABMultiValueCreateMutable(UInt32(kABMultiStringPropertyType)).takeRetainedValue() + for i in 0.. Bool { +open class AABubbleContactCellLayouter: AABubbleLayouter { + open func isSuitable(_ message: ACMessage) -> Bool { if (message.content is ACContactContent) { return true } @@ -206,19 +206,19 @@ public class AABubbleContactCellLayouter: AABubbleLayouter { return false } - public func cellClass() -> AnyClass { + open func cellClass() -> AnyClass { return AABubbleContactCell.self } - public func buildLayout(peer: ACPeer, message: ACMessage) -> AACellLayout { + open func buildLayout(_ peer: ACPeer, message: ACMessage) -> AACellLayout { let content = message.content as! ACContactContent var records = [String]() for i in 0.. Void in - self.progress.hidden = true - self.fileIcon.hidden = true + UIView.animate(withDuration: 0, animations: { () -> Void in + self.progress.isHidden = true + self.fileIcon.isHidden = true }) // Bind file @@ -138,44 +138,44 @@ public class AABubbleDocumentCell: AABubbleBaseFileCell, UIDocumentInteractionCo } } - public func documentDidTap() { + open func documentDidTap() { let content = bindedMessage!.content as! ACDocumentContent if let fileSource = content.getSource() as? ACFileRemoteSource { - Actor.requestStateWithFileId(fileSource.getFileReference().getFileId(), withCallback: AAFileCallback( + Actor.requestState(withFileId: fileSource.getFileReference().getFileId(), with: AAFileCallback( notDownloaded: { () -> () in - Actor.startDownloadingWithReference(fileSource.getFileReference()) + Actor.startDownloading(with: fileSource.getFileReference()) }, onDownloading: { (progress) -> () in - Actor.cancelDownloadingWithFileId(fileSource.getFileReference().getFileId()) + Actor.cancelDownloading(withFileId: fileSource.getFileReference().getFileId()) }, onDownloaded: { (reference) -> () in - let docController = UIDocumentInteractionController(URL: NSURL(fileURLWithPath: CocoaFiles.pathFromDescriptor(reference))) + let docController = UIDocumentInteractionController(url: URL(fileURLWithPath: CocoaFiles.pathFromDescriptor(reference))) docController.delegate = self - if (docController.presentPreviewAnimated(true)) { + if (docController.presentPreview(animated: true)) { return } })) } else if let fileSource = content.getSource() as? ACFileLocalSource { let rid = bindedMessage!.rid - Actor.requestUploadStateWithRid(rid, withCallback: AAUploadFileCallback( + Actor.requestUploadState(withRid: rid, with: AAUploadFileCallback( notUploaded: { () -> () in - Actor.resumeUploadWithRid(rid) + Actor.resumeUpload(withRid: rid) }, onUploading: { (progress) -> () in - Actor.pauseUploadWithRid(rid) + Actor.pauseUpload(withRid: rid) }, onUploadedClosure: { () -> () in - let docController = UIDocumentInteractionController(URL: NSURL(fileURLWithPath: CocoaFiles.pathFromDescriptor(fileSource.getFileDescriptor()))) + let docController = UIDocumentInteractionController(url: URL(fileURLWithPath: CocoaFiles.pathFromDescriptor(fileSource.getFileDescriptor()))) docController.delegate = self - if (docController.presentPreviewAnimated(true)) { + if (docController.presentPreview(animated: true)) { return } })) } } - public override func fileStateChanged(reference: String?, progress: Int?, isPaused: Bool, isUploading: Bool, selfGeneration: Int) { + open override func fileStateChanged(_ reference: String?, progress: Int?, isPaused: Bool, isUploading: Bool, selfGeneration: Int) { self.runOnUiThread(selfGeneration) { () -> () in if isUploading { if isPaused { @@ -219,7 +219,7 @@ public class AABubbleDocumentCell: AABubbleBaseFileCell, UIDocumentInteractionCo } } - public override func layoutContent(maxWidth: CGFloat, offsetX: CGFloat) { + open override func layoutContent(_ maxWidth: CGFloat, offsetX: CGFloat) { let insets = fullContentInsets let contentWidth = self.contentView.frame.width @@ -231,63 +231,63 @@ public class AABubbleDocumentCell: AABubbleBaseFileCell, UIDocumentInteractionCo let contentLeft = self.isOut ? contentWidth - 200 - insets.right - contentInsets.left : insets.left // Content - self.titleLabel.frame = CGRectMake(contentLeft + 62, 16 + top, 200 - 64, 22) - self.sizeLabel.frame = CGRectMake(contentLeft + 62, 16 + 22 + top, 200 - 64, 22) + self.titleLabel.frame = CGRect(x: contentLeft + 62, y: 16 + top, width: 200 - 64, height: 22) + self.sizeLabel.frame = CGRect(x: contentLeft + 62, y: 16 + 22 + top, width: 200 - 64, height: 22) // Progress state - let progressRect = CGRectMake(contentLeft + 8, 12 + top, 48, 48) + let progressRect = CGRect(x: contentLeft + 8, y: 12 + top, width: 48, height: 48) self.progress.frame = progressRect - self.fileIcon.frame = CGRectMake(contentLeft + 16, 20 + top, 32, 32) + self.fileIcon.frame = CGRect(x: contentLeft + 16, y: 20 + top, width: 32, height: 32) // Message state if (self.isOut) { - self.dateLabel.frame = CGRectMake(self.bubble.frame.maxX - 70 - self.bubblePadding, self.bubble.frame.maxY - 24, 46, 26) - self.statusView.frame = CGRectMake(self.bubble.frame.maxX - 24 - self.bubblePadding, self.bubble.frame.maxY - 24, 20, 26) - self.statusView.hidden = false + self.dateLabel.frame = CGRect(x: self.bubble.frame.maxX - 70 - self.bubblePadding, y: self.bubble.frame.maxY - 24, width: 46, height: 26) + self.statusView.frame = CGRect(x: self.bubble.frame.maxX - 24 - self.bubblePadding, y: self.bubble.frame.maxY - 24, width: 20, height: 26) + self.statusView.isHidden = false } else { - self.dateLabel.frame = CGRectMake(self.bubble.frame.maxX - 47 - self.bubblePadding, self.bubble.frame.maxY - 24, 46, 26) - self.statusView.hidden = true + self.dateLabel.frame = CGRect(x: self.bubble.frame.maxX - 47 - self.bubblePadding, y: self.bubble.frame.maxY - 24, width: 46, height: 26) + self.statusView.isHidden = true } } - public func documentInteractionControllerViewControllerForPreview(controller: UIDocumentInteractionController) -> UIViewController { + open func documentInteractionControllerViewControllerForPreview(_ controller: UIDocumentInteractionController) -> UIViewController { return self.controller } } -public class AABubbleDocumentCellLayout: AABubbleLayouter { +open class AABubbleDocumentCellLayout: AABubbleLayouter { - public func isSuitable(message: ACMessage) -> Bool { + open func isSuitable(_ message: ACMessage) -> Bool { return message.content is ACDocumentContent } - public func buildLayout(peer: ACPeer, message: ACMessage) -> AACellLayout { + open func buildLayout(_ peer: ACPeer, message: ACMessage) -> AACellLayout { return DocumentCellLayout(message: message, layouter: self) } - public func cellClass() -> AnyClass { + open func cellClass() -> AnyClass { return AABubbleDocumentCell.self } } -public class DocumentCellLayout: AACellLayout { +open class DocumentCellLayout: AACellLayout { - public let fileName: String - public let fileExt: String - public let fileSize: String + open let fileName: String + open let fileExt: String + open let fileSize: String - public let icon: UIImage - public let fastThumb: NSData? + open let icon: UIImage + open let fastThumb: Data? - public let autoDownload: Bool + open let autoDownload: Bool public init(fileName: String, fileExt: String, fileSize: Int, fastThumb: ACFastThumb?, date: Int64, autoDownload: Bool, layouter: AABubbleLayouter) { // File metadata self.fileName = fileName - self.fileExt = fileExt.lowercaseString + self.fileExt = fileExt.lowercased() self.fileSize = Actor.getFormatter().formatFileSize(jint(fileSize)) // Auto download flag @@ -300,37 +300,37 @@ public class DocumentCellLayout: AACellLayout { var fileName = "file_unknown" if (AAFileTypes[self.fileExt] != nil) { switch(AAFileTypes[self.fileExt]!) { - case AAFileType.Music: + case AAFileType.music: fileName = "file_music" break - case AAFileType.Doc: + case AAFileType.doc: fileName = "file_doc" break - case AAFileType.Spreadsheet: + case AAFileType.spreadsheet: fileName = "file_xls" break - case AAFileType.Video: + case AAFileType.video: fileName = "file_video" break - case AAFileType.Presentation: + case AAFileType.presentation: fileName = "file_ppt" break - case AAFileType.PDF: + case AAFileType.pdf: fileName = "file_pdf" break - case AAFileType.APK: + case AAFileType.apk: fileName = "file_apk" break - case AAFileType.RAR: + case AAFileType.rar: fileName = "file_rar" break - case AAFileType.ZIP: + case AAFileType.zip: fileName = "file_zip" break - case AAFileType.CSV: + case AAFileType.csv: fileName = "file_csv" break - case AAFileType.HTML: + case AAFileType.html: fileName = "file_html" break default: @@ -350,4 +350,4 @@ public class DocumentCellLayout: AACellLayout { public convenience init(message: ACMessage, layouter: AABubbleLayouter) { self.init(document: message.content as! ACDocumentContent, date: Int64(message.date), layouter: layouter) } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Cell/AABubbleLocationCell.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Cell/AABubbleLocationCell.swift index c097d1f92d..7c202a9f78 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Cell/AABubbleLocationCell.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Cell/AABubbleLocationCell.swift @@ -8,27 +8,27 @@ import MapKit private let mapWidth: CGFloat = 200 private let mapHeight: CGFloat = 160 -public class AABubbleLocationCell: AABubbleCell { +open class AABubbleLocationCell: AABubbleCell { - private let map = AAMapFastView(mapWidth: mapWidth, mapHeight: mapHeight) + fileprivate let map = AAMapFastView(mapWidth: mapWidth, mapHeight: mapHeight) - private let pin = UIImageView() - private let timeBg = UIImageView() - private let timeLabel = UILabel() - private let statusView = UIImageView() + fileprivate let pin = UIImageView() + fileprivate let timeBg = UIImageView() + fileprivate let timeLabel = UILabel() + fileprivate let statusView = UIImageView() - private var bindedLat: Double? = nil - private var bindedLon: Double? = nil + fileprivate var bindedLat: Double? = nil + fileprivate var bindedLon: Double? = nil public init(frame: CGRect) { super.init(frame: frame, isFullSize: false) timeBg.image = ActorSDK.sharedActor().style.statusBackgroundImage - timeLabel.font = UIFont.italicSystemFontOfSize(11) + timeLabel.font = UIFont.italicSystemFont(ofSize: 11) timeLabel.textColor = appStyle.chatMediaDateColor - statusView.contentMode = UIViewContentMode.Center + statusView.contentMode = UIViewContentMode.center pin.image = UIImage.bundled("LocationPin") @@ -42,7 +42,7 @@ public class AABubbleLocationCell: AABubbleCell { contentInsets = UIEdgeInsets(top: 1, left: 1, bottom: 1, right: 1) map.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(AABubbleLocationCell.mapDidTap))) - map.userInteractionEnabled = true + map.isUserInteractionEnabled = true } public required init(coder aDecoder: NSCoder) { @@ -52,10 +52,10 @@ public class AABubbleLocationCell: AABubbleCell { func mapDidTap() { let url = "http://maps.apple.com/?q=\(bindedLat!),\(bindedLon!)" // print("url: \(url)") - UIApplication.sharedApplication().openURL(NSURL(string: url)!) + UIApplication.shared.openURL(URL(string: url)!) } - public override func bind(message: ACMessage, receiveDate: jlong, readDate: jlong, reuse: Bool, cellLayout: AACellLayout, setting: AACellSetting) { + open override func bind(_ message: ACMessage, receiveDate: jlong, readDate: jlong, reuse: Bool, cellLayout: AACellLayout, setting: AACellSetting) { let layout = cellLayout as! AALocationCellLayout @@ -72,9 +72,9 @@ public class AABubbleLocationCell: AABubbleCell { // Bind bubble if (self.isOut) { - bindBubbleType(BubbleType.MediaOut, isCompact: false) + bindBubbleType(BubbleType.mediaOut, isCompact: false) } else { - bindBubbleType(BubbleType.MediaIn, isCompact: false) + bindBubbleType(BubbleType.mediaIn, isCompact: false) } } @@ -85,7 +85,7 @@ public class AABubbleLocationCell: AABubbleCell { // Update status if (isOut) { - statusView.hidden = false + statusView.isHidden = false switch(message.messageState.toNSEnum()) { case .SENT: if message.sortDate <= readDate { @@ -112,43 +112,43 @@ public class AABubbleLocationCell: AABubbleCell { break } } else { - statusView.hidden = true + statusView.isHidden = true } } - public override func layoutContent(maxWidth: CGFloat, offsetX: CGFloat) { + open override func layoutContent(_ maxWidth: CGFloat, offsetX: CGFloat) { let insets = fullContentInsets let contentWidth = self.contentView.frame.width layoutBubble(mapWidth, contentHeight: mapHeight) if isOut { - map.frame = CGRectMake(contentWidth - insets.right - mapWidth , insets.top, mapWidth, mapHeight) + map.frame = CGRect(x: contentWidth - insets.right - mapWidth , y: insets.top, width: mapWidth, height: mapHeight) } else { - map.frame = CGRectMake(insets.left, insets.top, mapWidth, mapHeight) + map.frame = CGRect(x: insets.left, y: insets.top, width: mapWidth, height: mapHeight) } - timeLabel.frame = CGRectMake(0, 0, 1000, 1000) + timeLabel.frame = CGRect(x: 0, y: 0, width: 1000, height: 1000) timeLabel.sizeToFit() let timeWidth = (isOut ? 23 : 0) + timeLabel.bounds.width let timeHeight: CGFloat = 20 - timeLabel.frame = CGRectMake(map.frame.maxX - timeWidth - 18, map.frame.maxY - timeHeight - 6, timeLabel.frame.width, timeHeight) + timeLabel.frame = CGRect(x: map.frame.maxX - timeWidth - 18, y: map.frame.maxY - timeHeight - 6, width: timeLabel.frame.width, height: timeHeight) if (isOut) { - statusView.frame = CGRectMake(timeLabel.frame.maxX, timeLabel.frame.minY, 23, timeHeight) + statusView.frame = CGRect(x: timeLabel.frame.maxX, y: timeLabel.frame.minY, width: 23, height: timeHeight) } - pin.frame = CGRectMake((map.width - pin.image!.size.width)/2, (map.height / 2 - pin.image!.size.height), - pin.image!.size.width, pin.image!.size.height) + pin.frame = CGRect(x: (map.width - pin.image!.size.width)/2, y: (map.height / 2 - pin.image!.size.height), + width: pin.image!.size.width, height: pin.image!.size.height) - timeBg.frame = CGRectMake(timeLabel.frame.minX - 4, timeLabel.frame.minY - 1, timeWidth + 8, timeHeight + 2) + timeBg.frame = CGRect(x: timeLabel.frame.minX - 4, y: timeLabel.frame.minY - 1, width: timeWidth + 8, height: timeHeight + 2) } } -public class AALocationCellLayout: AACellLayout { +open class AALocationCellLayout: AACellLayout { let latitude: Double let longitude: Double @@ -160,21 +160,21 @@ public class AALocationCellLayout: AACellLayout { } } -public class AABubbleLocationCellLayouter: AABubbleLayouter { +open class AABubbleLocationCellLayouter: AABubbleLayouter { - public func isSuitable(message: ACMessage) -> Bool { + open func isSuitable(_ message: ACMessage) -> Bool { if (message.content is ACLocationContent) { return true } return false } - public func buildLayout(peer: ACPeer, message: ACMessage) -> AACellLayout { + open func buildLayout(_ peer: ACPeer, message: ACMessage) -> AACellLayout { let content = message.content as! ACLocationContent return AALocationCellLayout(latitude: Double(content.getLatitude()), longitude: Double(content.getLongitude()), date: Int64(message.date), layouter: self) } - public func cellClass() -> AnyClass { + open func cellClass() -> AnyClass { return AABubbleLocationCell.self } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Cell/AABubbleMediaCell.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Cell/AABubbleMediaCell.swift index d555ffd12b..8856d0ea85 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Cell/AABubbleMediaCell.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Cell/AABubbleMediaCell.swift @@ -8,12 +8,12 @@ import YYImage import YYWebImage import YYCategories -public class AABubbleMediaCell : AABubbleBaseFileCell, NYTPhotosViewControllerDelegate { +open class AABubbleMediaCell : AABubbleBaseFileCell, NYTPhotosViewControllerDelegate { // Views var preview = YYAnimatedImageView() - let progress = AAProgressView(size: CGSizeMake(64, 64)) + let progress = AAProgressView(size: CGSize(width: 64, height: 64)) let timeBg = UIImageView() let timeLabel = UILabel() let statusView = UIImageView() @@ -35,14 +35,14 @@ public class AABubbleMediaCell : AABubbleBaseFileCell, NYTPhotosViewControllerDe super.init(frame: frame, isFullSize: false) preview.autoPlayAnimatedImage = true - preview.runloopMode = NSDefaultRunLoopMode + preview.runloopMode = RunLoopMode.defaultRunLoopMode.rawValue timeBg.image = Imaging.roundedImage(ActorSDK.sharedActor().style.chatMediaDateBgColor, radius: 10) - timeLabel.font = UIFont.italicSystemFontOfSize(11) + timeLabel.font = UIFont.italicSystemFont(ofSize: 11) timeLabel.textColor = appStyle.chatMediaDateColor - statusView.contentMode = UIViewContentMode.Center + statusView.contentMode = UIViewContentMode.center contentView.addSubview(preview) contentView.addSubview(progress) @@ -53,10 +53,10 @@ public class AABubbleMediaCell : AABubbleBaseFileCell, NYTPhotosViewControllerDe contentView.addSubview(playView) preview.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(AABubbleMediaCell.mediaDidTap))) - preview.userInteractionEnabled = true + preview.isUserInteractionEnabled = true contentInsets = UIEdgeInsets(top: 2, left: 2, bottom: 2, right: 2) - playView.userInteractionEnabled = false + playView.isUserInteractionEnabled = false } public required init(coder aDecoder: NSCoder) { @@ -65,7 +65,7 @@ public class AABubbleMediaCell : AABubbleBaseFileCell, NYTPhotosViewControllerDe // Binding - public override func bind(message: ACMessage, receiveDate: jlong, readDate: jlong, reuse: Bool, cellLayout: AACellLayout, setting: AACellSetting) { + open override func bind(_ message: ACMessage, receiveDate: jlong, readDate: jlong, reuse: Bool, cellLayout: AACellLayout, setting: AACellSetting) { self.bindedLayout = cellLayout as! MediaCellLayout bubbleInsets = UIEdgeInsets( @@ -78,9 +78,9 @@ public class AABubbleMediaCell : AABubbleBaseFileCell, NYTPhotosViewControllerDe // Bind bubble if (self.isOut) { - bindBubbleType(BubbleType.MediaOut, isCompact: false) + bindBubbleType(BubbleType.mediaOut, isCompact: false) } else { - bindBubbleType(BubbleType.MediaIn, isCompact: false) + bindBubbleType(BubbleType.mediaIn, isCompact: false) } // Reset content state @@ -98,7 +98,7 @@ public class AABubbleMediaCell : AABubbleBaseFileCell, NYTPhotosViewControllerDe self.preview.alpha = 0 // Show/Hide play button - self.playView.hidden = true + self.playView.isHidden = true // Rounding Animations if (message.content is ACAnimationContent) { @@ -133,7 +133,7 @@ public class AABubbleMediaCell : AABubbleBaseFileCell, NYTPhotosViewControllerDe // Update status if (isOut) { - statusView.hidden = false + statusView.isHidden = false switch(message.messageState.toNSEnum()) { case .SENT: if message.sortDate <= readDate { @@ -157,13 +157,13 @@ public class AABubbleMediaCell : AABubbleBaseFileCell, NYTPhotosViewControllerDe break } } else { - statusView.hidden = true + statusView.isHidden = true } } // File state binding - public override func fileStateChanged(reference: String?, progress: Int?, isPaused: Bool, isUploading: Bool, selfGeneration: Int) { + open override func fileStateChanged(_ reference: String?, progress: Int?, isPaused: Bool, isUploading: Bool, selfGeneration: Int) { // Loading Fast Thumb // 1. Check Current generation @@ -176,7 +176,7 @@ public class AABubbleMediaCell : AABubbleBaseFileCell, NYTPhotosViewControllerDe })) { return } if (needToLoadThumb) { - let loadedThumb = YYImage(data: bindedLayout.fastThumb!)!.imageByBlurSoft()! + let loadedThumb = YYImage(data: bindedLayout.fastThumb!)!.byBlurSoft()! .roundCorners(bindedLayout.screenSize.width, h: bindedLayout.screenSize.height, roundSize: 14) if !(runOnUiThread(selfGeneration, closure: { self.thumb = loadedThumb @@ -201,9 +201,9 @@ public class AABubbleMediaCell : AABubbleBaseFileCell, NYTPhotosViewControllerDe if let r = reference { if !self.previewShown { self.previewShown = true - self.preview.yy_setImageWithURL(NSURL.fileURLWithPath(CocoaFiles.pathFromDescriptor(r)), + self.preview.yy_setImage(with: URL(fileURLWithPath: CocoaFiles.pathFromDescriptor(r)), placeholder: self.thumb, - options: YYWebImageOptions.SetImageWithFadeAnimation, + options: YYWebImageOptions.setImageWithFadeAnimation, progress: nil, transform: { (img, url) -> UIImage? in return img.roundCorners(self.bindedLayout.screenSize.width, h: self.bindedLayout.screenSize.height, roundSize: 14) @@ -221,8 +221,8 @@ public class AABubbleMediaCell : AABubbleBaseFileCell, NYTPhotosViewControllerDe if let r = reference { if !self.previewShown { self.previewShown = true - self.preview.yy_setImageWithURL(NSURL.fileURLWithPath(CocoaFiles.pathFromDescriptor(r)), - placeholder: self.thumb, options: YYWebImageOptions.SetImageWithFadeAnimation, + self.preview.yy_setImage(with: URL(fileURLWithPath: CocoaFiles.pathFromDescriptor(r)), + placeholder: self.thumb, options: YYWebImageOptions.setImageWithFadeAnimation, progress: nil, transform: nil,completion: nil) self.preview.showViewAnimated() } @@ -255,12 +255,12 @@ public class AABubbleMediaCell : AABubbleBaseFileCell, NYTPhotosViewControllerDe // Play Button if self.bindedMessage?.content is ACAnimationContent { if ActorSDK.sharedActor().isGIFAutoplayEnabled { - self.playView.hidden = true + self.playView.isHidden = true } else { - self.playView.hidden = false + self.playView.isHidden = false } } else { - self.playView.hidden = !(self.bindedMessage?.content is ACVideoContent) + self.playView.isHidden = !(self.bindedMessage?.content is ACVideoContent) } } else { if isPaused { @@ -279,62 +279,62 @@ public class AABubbleMediaCell : AABubbleBaseFileCell, NYTPhotosViewControllerDe // Media Action - public func mediaDidTap() { + open func mediaDidTap() { let content = bindedMessage!.content as! ACDocumentContent if let fileSource = content.getSource() as? ACFileRemoteSource { - Actor.requestStateWithFileId(fileSource.getFileReference().getFileId(), withCallback: AAFileCallback( + Actor.requestState(withFileId: fileSource.getFileReference().getFileId(), with: AAFileCallback( notDownloaded: { () -> () in - Actor.startDownloadingWithReference(fileSource.getFileReference()) + Actor.startDownloading(with: fileSource.getFileReference()) }, onDownloading: { (progress) -> () in - Actor.cancelDownloadingWithFileId(fileSource.getFileReference().getFileId()) + Actor.cancelDownloading(withFileId: fileSource.getFileReference().getFileId()) }, onDownloaded: { (reference) -> () in if content is ACPhotoContent { if let img = UIImage(contentsOfFile: CocoaFiles.pathFromDescriptor(reference)) { let previewImage = PreviewImage(image: img) let previewController = AAPhotoPreviewController(photo: previewImage, fromView: self.preview) previewController.autoShowBadge = true - self.controller.presentViewController(previewController, animated: true, completion: nil) + self.controller.present(previewController, animated: true, completion: nil) } } else if content is ACVideoContent { self.controller.playVideoFromPath(CocoaFiles.pathFromDescriptor(reference)) } else if self.bindedMessage?.content is ACAnimationContent { if !ActorSDK.sharedActor().isGIFAutoplayEnabled { - if self.playView.hidden { + if self.playView.isHidden { self.preview.stopAnimating() - self.playView.hidden = false + self.playView.isHidden = false } else { self.preview.startAnimating() - self.playView.hidden = true + self.playView.isHidden = true } } } })) } else if let fileSource = content.getSource() as? ACFileLocalSource { let rid = bindedMessage!.rid - Actor.requestUploadStateWithRid(rid, withCallback: AAUploadFileCallback( + Actor.requestUploadState(withRid: rid, with: AAUploadFileCallback( notUploaded: { () -> () in - Actor.resumeUploadWithRid(rid) + Actor.resumeUpload(withRid: rid) }, onUploading: { (progress) -> () in - Actor.pauseUploadWithRid(rid) + Actor.pauseUpload(withRid: rid) }, onUploadedClosure: { () -> () in if content is ACPhotoContent { if let img = UIImage(contentsOfFile: CocoaFiles.pathFromDescriptor(fileSource.getFileDescriptor())) { let previewImage = PreviewImage(image: img) let previewController = AAPhotoPreviewController(photo: previewImage, fromView: self.preview) previewController.autoShowBadge = true - self.controller.presentViewController(previewController, animated: true, completion: nil) + self.controller.present(previewController, animated: true, completion: nil) } } else if content is ACVideoContent { self.controller.playVideoFromPath(CocoaFiles.pathFromDescriptor(fileSource.getFileDescriptor())) } else if self.bindedMessage?.content is ACAnimationContent { if !ActorSDK.sharedActor().isGIFAutoplayEnabled { - if self.playView.hidden { + if self.playView.isHidden { self.preview.stopAnimating() - self.playView.hidden = false + self.playView.isHidden = false } else { self.preview.startAnimating() - self.playView.hidden = true + self.playView.isHidden = true } } } @@ -344,7 +344,7 @@ public class AABubbleMediaCell : AABubbleBaseFileCell, NYTPhotosViewControllerDe // Layouting - public override func layoutContent(maxWidth: CGFloat, offsetX: CGFloat) { + open override func layoutContent(_ maxWidth: CGFloat, offsetX: CGFloat) { let insets = fullContentInsets let contentWidth = self.contentView.frame.width _ = self.contentView.frame.height @@ -354,52 +354,52 @@ public class AABubbleMediaCell : AABubbleBaseFileCell, NYTPhotosViewControllerDe layoutBubble(bubbleWidth, contentHeight: bubbleHeight) if (isOut) { - preview.frame = CGRectMake(contentWidth - insets.left - bubbleWidth, insets.top, bubbleWidth, bubbleHeight) + preview.frame = CGRect(x: contentWidth - insets.left - bubbleWidth, y: insets.top, width: bubbleWidth, height: bubbleHeight) } else { - preview.frame = CGRectMake(insets.left, insets.top, bubbleWidth, bubbleHeight) + preview.frame = CGRect(x: insets.left, y: insets.top, width: bubbleWidth, height: bubbleHeight) } playView.centerIn(preview.frame) - progress.frame = CGRectMake(preview.frame.origin.x + preview.frame.width/2 - 32, preview.frame.origin.y + preview.frame.height/2 - 32, 64, 64) + progress.frame = CGRect(x: preview.frame.origin.x + preview.frame.width/2 - 32, y: preview.frame.origin.y + preview.frame.height/2 - 32, width: 64, height: 64) - timeLabel.frame = CGRectMake(0, 0, 1000, 1000) + timeLabel.frame = CGRect(x: 0, y: 0, width: 1000, height: 1000) timeLabel.sizeToFit() let timeWidth = (isOut ? 23 : 0) + timeLabel.bounds.width let timeHeight: CGFloat = 20 - timeLabel.frame = CGRectMake(preview.frame.maxX - timeWidth - 8, preview.frame.maxY - timeHeight - 4, timeLabel.frame.width, timeHeight) + timeLabel.frame = CGRect(x: preview.frame.maxX - timeWidth - 8, y: preview.frame.maxY - timeHeight - 4, width: timeLabel.frame.width, height: timeHeight) if (isOut) { - statusView.frame = CGRectMake(timeLabel.frame.maxX, timeLabel.frame.minY, 23, timeHeight) + statusView.frame = CGRect(x: timeLabel.frame.maxX, y: timeLabel.frame.minY, width: 23, height: timeHeight) } - timeBg.frame = CGRectMake(timeLabel.frame.minX - 6, timeLabel.frame.minY, timeWidth + 10, timeHeight) + timeBg.frame = CGRect(x: timeLabel.frame.minX - 6, y: timeLabel.frame.minY, width: timeWidth + 10, height: timeHeight) } // Photo preview - public func photosViewController(photosViewController: NYTPhotosViewController, referenceViewForPhoto photo: NYTPhoto) -> UIView? { + open func photosViewController(_ photosViewController: NYTPhotosViewController, referenceViewFor photo: NYTPhoto) -> UIView? { return self.preview } - public func photosViewControllerWillDismiss(photosViewController: NYTPhotosViewController) { + open func photosViewControllerWillDismiss(_ photosViewController: NYTPhotosViewController) { // (UIApplication.sharedApplication().delegate as! AppDelegate).showBadge() - UIApplication.sharedApplication().setStatusBarHidden(false, withAnimation: UIStatusBarAnimation.Fade) + UIApplication.shared.setStatusBarHidden(false, with: UIStatusBarAnimation.fade) } } /** Media cell layout */ -public class MediaCellLayout: AACellLayout { +open class MediaCellLayout: AACellLayout { - public let fastThumb: NSData? - public let contentSize: CGSize - public let screenSize: CGSize - public let autoDownload: Bool - public let duration: Int? + open let fastThumb: Data? + open let contentSize: CGSize + open let screenSize: CGSize + open let autoDownload: Bool + open let duration: Int? /** Creting layout for media bubble @@ -410,7 +410,7 @@ public class MediaCellLayout: AACellLayout { self.duration = duration // Saving content size - self.contentSize = CGSizeMake(width, height) + self.contentSize = CGSize(width: width, height: height) // Saving autodownload flag self.autoDownload = autoDownload @@ -465,9 +465,9 @@ public class MediaCellLayout: AACellLayout { /** Layouter for media bubbles */ -public class AABubbleMediaCellLayouter: AABubbleLayouter { +open class AABubbleMediaCellLayouter: AABubbleLayouter { - public func isSuitable(message: ACMessage) -> Bool { + open func isSuitable(_ message: ACMessage) -> Bool { if message.content is ACPhotoContent { return true } @@ -480,11 +480,11 @@ public class AABubbleMediaCellLayouter: AABubbleLayouter { return false } - public func buildLayout(peer: ACPeer, message: ACMessage) -> AACellLayout { + open func buildLayout(_ peer: ACPeer, message: ACMessage) -> AACellLayout { return MediaCellLayout(message: message, layouter: self) } - public func cellClass() -> AnyClass { + open func cellClass() -> AnyClass { return AABubbleMediaCell.self } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Cell/AABubbleServiceCell.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Cell/AABubbleServiceCell.swift index 4cc732d057..ae194d6b83 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Cell/AABubbleServiceCell.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Cell/AABubbleServiceCell.swift @@ -4,25 +4,25 @@ import Foundation -public class AABubbleServiceCell : AABubbleCell { +open class AABubbleServiceCell : AABubbleCell { - private static let serviceBubbleFont = UIFont.boldSystemFontOfSize(12) - private static let maxServiceTextWidth: CGFloat = 260 + fileprivate static let serviceBubbleFont = UIFont.boldSystemFont(ofSize: 12) + fileprivate static let maxServiceTextWidth: CGFloat = 260 - private let serviceText = YYLabel() + fileprivate let serviceText = YYLabel() - private var bindedLayout: ServiceCellLayout! + fileprivate var bindedLayout: ServiceCellLayout! public init(frame: CGRect) { super.init(frame: frame, isFullSize: true) // Configuring service label serviceText.font = AABubbleServiceCell.serviceBubbleFont; - serviceText.lineBreakMode = .ByWordWrapping; + serviceText.lineBreakMode = .byWordWrapping; serviceText.numberOfLines = 0; serviceText.textColor = appStyle.chatServiceTextColor - serviceText.contentMode = UIViewContentMode.Center - serviceText.textAlignment = NSTextAlignment.Center + serviceText.contentMode = UIViewContentMode.center + serviceText.textAlignment = NSTextAlignment.center contentView.addSubview(serviceText) // Setting content and bubble insets @@ -30,14 +30,14 @@ public class AABubbleServiceCell : AABubbleCell { bubbleInsets = UIEdgeInsets(top: 3, left: 0, bottom: 3, right: 0) // Setting bubble background - bindBubbleType(.Service, isCompact: false) + bindBubbleType(.service, isCompact: false) } public required init(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - public override func bind(message: ACMessage, receiveDate: jlong, readDate: jlong, reuse: Bool, cellLayout: AACellLayout, setting: AACellSetting) { + open override func bind(_ message: ACMessage, receiveDate: jlong, readDate: jlong, reuse: Bool, cellLayout: AACellLayout, setting: AACellSetting) { self.bindedLayout = cellLayout as! ServiceCellLayout if (!reuse) { @@ -45,20 +45,20 @@ public class AABubbleServiceCell : AABubbleCell { } } - public override func layoutContent(maxWidth: CGFloat, offsetX: CGFloat) { + open override func layoutContent(_ maxWidth: CGFloat, offsetX: CGFloat) { let insets = fullContentInsets let contentWidth = self.contentView.frame.width let serviceWidth = bindedLayout.textSize.width let serviceHeight = bindedLayout.textSize.height - serviceText.frame = CGRectMake((contentWidth - serviceWidth) / 2.0, insets.top, serviceWidth, serviceHeight); + serviceText.frame = CGRect(x: (contentWidth - serviceWidth) / 2.0, y: insets.top, width: serviceWidth, height: serviceHeight); layoutBubble(serviceWidth, contentHeight: serviceHeight) } } -public class ServiceCellLayout: AACellLayout { +open class ServiceCellLayout: AACellLayout { var text: String var textSize: CGSize @@ -76,27 +76,27 @@ public class ServiceCellLayout: AACellLayout { } } -public class AABubbleServiceCellLayouter: AABubbleLayouter { +open class AABubbleServiceCellLayouter: AABubbleLayouter { - public func isSuitable(message: ACMessage) -> Bool { + open func isSuitable(_ message: ACMessage) -> Bool { return message.content is ACServiceContent } - public func buildLayout(peer: ACPeer, message: ACMessage) -> AACellLayout { + open func buildLayout(_ peer: ACPeer, message: ACMessage) -> AACellLayout { let isChannel: Bool if peer.isGroup { - isChannel = Actor.getGroupWithGid(peer.peerId).groupType == ACGroupType.CHANNEL() + isChannel = Actor.getGroupWithGid(peer.peerId).groupType == ACGroupType.channel() } else { isChannel = false } - let serviceText = Actor.getFormatter().formatFullServiceMessageWithSenderId(message.senderId, withContent: message.content as! ACServiceContent, withIsChannel: isChannel) + let serviceText = Actor.getFormatter().formatFullServiceMessage(withSenderId: message.senderId, with: message.content as! ACServiceContent, withIsChannel: isChannel) - return ServiceCellLayout(text: serviceText, date: Int64(message.date), layouter: self) + return ServiceCellLayout(text: serviceText!, date: Int64(message.date), layouter: self) } - public func cellClass() -> AnyClass { + open func cellClass() -> AnyClass { return AABubbleServiceCell.self } } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Cell/AABubbleStickerCell.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Cell/AABubbleStickerCell.swift index e6449f9271..ba714a00d6 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Cell/AABubbleStickerCell.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Cell/AABubbleStickerCell.swift @@ -6,7 +6,7 @@ import UIKit import VBFPopFlatButton import YYImage -public class AABubbleStickerCell: AABubbleBaseFileCell { +open class AABubbleStickerCell: AABubbleBaseFileCell { // Views @@ -20,7 +20,7 @@ public class AABubbleStickerCell: AABubbleBaseFileCell { var bindedLayout: StikerCellLayout! var contentLoaded = false - private var callback: AAFileCallback? = nil + fileprivate var callback: AAFileCallback? = nil // Constructors @@ -29,12 +29,12 @@ public class AABubbleStickerCell: AABubbleBaseFileCell { timeBg.image = ActorSDK.sharedActor().style.statusBackgroundImage - timeLabel.font = UIFont.italicSystemFontOfSize(11) + timeLabel.font = UIFont.italicSystemFont(ofSize: 11) timeLabel.textColor = appStyle.chatMediaDateColor - statusView.contentMode = UIViewContentMode.Center + statusView.contentMode = UIViewContentMode.center - preview.contentMode = .ScaleAspectFit + preview.contentMode = .scaleAspectFit contentView.addSubview(preview) @@ -43,7 +43,7 @@ public class AABubbleStickerCell: AABubbleBaseFileCell { contentView.addSubview(statusView) preview.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(AABubbleStickerCell.mediaDidTap))) - preview.userInteractionEnabled = true + preview.isUserInteractionEnabled = true contentInsets = UIEdgeInsets(top: 1, left: 1, bottom: 1, right: 1) } @@ -54,7 +54,7 @@ public class AABubbleStickerCell: AABubbleBaseFileCell { // Binding - public override func bind(message: ACMessage, receiveDate: jlong, readDate: jlong, reuse: Bool, cellLayout: AACellLayout, setting: AACellSetting) { + open override func bind(_ message: ACMessage, receiveDate: jlong, readDate: jlong, reuse: Bool, cellLayout: AACellLayout, setting: AACellSetting) { self.bindedLayout = cellLayout as! StikerCellLayout bubbleInsets = UIEdgeInsets( @@ -66,7 +66,7 @@ public class AABubbleStickerCell: AABubbleBaseFileCell { if (!reuse) { - bindBubbleType(BubbleType.Sticker, isCompact: false) + bindBubbleType(BubbleType.sticker, isCompact: false) // Reset content state preview.image = nil @@ -82,7 +82,7 @@ public class AABubbleStickerCell: AABubbleBaseFileCell { // Update status if (isOut) { - statusView.hidden = false + statusView.isHidden = false switch(message.messageState.toNSEnum()) { case .SENT: if message.sortDate <= readDate { @@ -106,13 +106,13 @@ public class AABubbleStickerCell: AABubbleBaseFileCell { break; } } else { - statusView.hidden = true + statusView.isHidden = true } } // File state binding - public override func fileStateChanged(reference: String?, progress: Int?, isPaused: Bool, isUploading: Bool, selfGeneration: Int) { + open override func fileStateChanged(_ reference: String?, progress: Int?, isPaused: Bool, isUploading: Bool, selfGeneration: Int) { if let r = reference { if (contentLoaded) { return @@ -133,14 +133,14 @@ public class AABubbleStickerCell: AABubbleBaseFileCell { // Media Action - public func mediaDidTap() { + open func mediaDidTap() { } // Layouting - public override func layoutContent(maxWidth: CGFloat, offsetX: CGFloat) { + open override func layoutContent(_ maxWidth: CGFloat, offsetX: CGFloat) { let insets = fullContentInsets let contentWidth = self.contentView.frame.width _ = self.contentView.frame.height @@ -150,26 +150,26 @@ public class AABubbleStickerCell: AABubbleBaseFileCell { layoutBubble(bubbleWidth, contentHeight: bubbleHeight) if (isOut) { - preview.frame = CGRectMake(contentWidth - insets.left - bubbleWidth, insets.top, bubbleWidth, bubbleHeight) + preview.frame = CGRect(x: contentWidth - insets.left - bubbleWidth, y: insets.top, width: bubbleWidth, height: bubbleHeight) } else { - preview.frame = CGRectMake(insets.left, insets.top, bubbleWidth, bubbleHeight) + preview.frame = CGRect(x: insets.left, y: insets.top, width: bubbleWidth, height: bubbleHeight) } //progress.frame = CGRectMake(preview.frame.origin.x + preview.frame.width/2 - 32, preview.frame.origin.y + preview.frame.height/2 - 32, 64, 64) - timeLabel.frame = CGRectMake(0, 0, 1000, 1000) + timeLabel.frame = CGRect(x: 0, y: 0, width: 1000, height: 1000) timeLabel.sizeToFit() let timeWidth = (isOut ? 23 : 0) + timeLabel.bounds.width let timeHeight: CGFloat = 20 - timeLabel.frame = CGRectMake(preview.frame.maxX - timeWidth - 18, preview.frame.maxY - timeHeight - 6, timeLabel.frame.width, timeHeight) + timeLabel.frame = CGRect(x: preview.frame.maxX - timeWidth - 18, y: preview.frame.maxY - timeHeight - 6, width: timeLabel.frame.width, height: timeHeight) if (isOut) { - statusView.frame = CGRectMake(timeLabel.frame.maxX, timeLabel.frame.minY, 23, timeHeight) + statusView.frame = CGRect(x: timeLabel.frame.maxX, y: timeLabel.frame.minY, width: 23, height: timeHeight) } - timeBg.frame = CGRectMake(timeLabel.frame.minX - 4, timeLabel.frame.minY - 1, timeWidth + 8, timeHeight + 2) + timeBg.frame = CGRect(x: timeLabel.frame.minX - 4, y: timeLabel.frame.minY - 1, width: timeWidth + 8, height: timeHeight + 2) } } @@ -177,12 +177,12 @@ public class AABubbleStickerCell: AABubbleBaseFileCell { /** Media cell layout */ -public class StikerCellLayout: AACellLayout { +open class StikerCellLayout: AACellLayout { // public let fastThumb: NSData? - public let contentSize: CGSize - public let screenSize: CGSize - public let autoDownload: Bool + open let contentSize: CGSize + open let screenSize: CGSize + open let autoDownload: Bool /** Creting layout for media bubble @@ -190,7 +190,7 @@ public class StikerCellLayout: AACellLayout { public init(id: Int64, width: CGFloat, height:CGFloat, date: Int64, stickerContent: ACStickerContent?, autoDownload: Bool, layouter: AABubbleLayouter) { // Saving content size - self.contentSize = CGSizeMake(width, height) + self.contentSize = CGSize(width: width, height: height) // Saving autodownload flag self.autoDownload = autoDownload @@ -228,9 +228,9 @@ public class StikerCellLayout: AACellLayout { /** Layouter for media bubbles */ -public class AABubbleStickerCellLayouter: AABubbleLayouter { +open class AABubbleStickerCellLayouter: AABubbleLayouter { - public func isSuitable(message: ACMessage) -> Bool { + open func isSuitable(_ message: ACMessage) -> Bool { if message.content is ACStickerContent { return true } @@ -238,11 +238,11 @@ public class AABubbleStickerCellLayouter: AABubbleLayouter { return false } - public func buildLayout(peer: ACPeer, message: ACMessage) -> AACellLayout { + open func buildLayout(_ peer: ACPeer, message: ACMessage) -> AACellLayout { return StikerCellLayout(message: message, layouter: self) } - public func cellClass() -> AnyClass { + open func cellClass() -> AnyClass { return AABubbleStickerCell.self } } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Cell/AABubbleTextCell.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Cell/AABubbleTextCell.swift index 66c3a70805..e1e465093c 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Cell/AABubbleTextCell.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Cell/AABubbleTextCell.swift @@ -6,7 +6,7 @@ import Foundation import UIKit import YYImage -public class AABubbleTextCell : AABubbleCell { +open class AABubbleTextCell : AABubbleCell { // TODO: Better max width calculations @@ -15,25 +15,25 @@ public class AABubbleTextCell : AABubbleCell { static let fontItalic = UIFont.italicTextFontOfSize(fontSize) static let fontBold = UIFont.boldTextFontOfSize(fontSize) - private static let dateFont = UIFont.italicSystemFontOfSize(11) - private static let senderFont = UIFont.boldSystemFontOfSize(15) + fileprivate static let dateFont = UIFont.italicSystemFont(ofSize: 11) + fileprivate static let senderFont = UIFont.boldSystemFont(ofSize: 15) static let bubbleFont = fontRegular static let bubbleFontUnsupported = fontItalic static let senderHeight = CGFloat(20) - private let messageText = YYLabel() - private let senderNameLabel = YYLabel() - private let dateText = YYLabel() - private let statusView = UIImageView() + fileprivate let messageText = YYLabel() + fileprivate let senderNameLabel = YYLabel() + fileprivate let dateText = YYLabel() + fileprivate let statusView = UIImageView() - private var needRelayout = true - private var isClanchTop:Bool = false - private var isClanchBottom:Bool = false + fileprivate var needRelayout = true + fileprivate var isClanchTop:Bool = false + fileprivate var isClanchBottom:Bool = false - private var dateWidth: CGFloat = 0 + fileprivate var dateWidth: CGFloat = 0 - private var cellLayout: TextCellLayout! + fileprivate var cellLayout: TextCellLayout! public init(frame: CGRect) { super.init(frame: frame, isFullSize: false) @@ -44,20 +44,20 @@ public class AABubbleTextCell : AABubbleCell { messageText.clearContentsBeforeAsynchronouslyDisplay = true messageText.highlightTapAction = { (containerView: UIView, text: NSAttributedString, range: NSRange, rect: CGRect) -> () in - let attributes = text.attributesAtIndex(range.location, effectiveRange: nil) + let attributes = text.attributes(at: range.location, effectiveRange: nil) if let attrs = attributes["YYTextHighlight"] as? YYTextHighlight { if let url = attrs.userInfo!["url"] as? String { - self.openUrl(NSURL(string: url)!) + self.openUrl(URL(string: url)!) } } } messageText.highlightLongPressAction = { (containerView: UIView, text: NSAttributedString, range: NSRange, rect: CGRect) -> () in self.bubble - let attributes = text.attributesAtIndex(range.location, effectiveRange: nil) + let attributes = text.attributes(at: range.location, effectiveRange: nil) if let attrs = attributes["YYTextHighlight"] as? YYTextHighlight { if let url = attrs.userInfo!["url"] as? String { - self.urlLongTap(NSURL(string: url)!) + self.urlLongTap(URL(string: url)!) } } } @@ -78,7 +78,7 @@ public class AABubbleTextCell : AABubbleCell { // dateText.numberOfLines = 1 // dateText.textAlignment = .Right - statusView.contentMode = UIViewContentMode.Center + statusView.contentMode = UIViewContentMode.center contentView.addSubview(messageText) contentView.addSubview(dateText) @@ -92,7 +92,7 @@ public class AABubbleTextCell : AABubbleCell { // Data binding - public override func bind(message: ACMessage, receiveDate: jlong, readDate: jlong, reuse: Bool, cellLayout: AACellLayout, setting: AACellSetting) { + open override func bind(_ message: ACMessage, receiveDate: jlong, readDate: jlong, reuse: Bool, cellLayout: AACellLayout, setting: AACellSetting) { // Saving cell settings self.cellLayout = cellLayout as! TextCellLayout @@ -109,17 +109,17 @@ public class AABubbleTextCell : AABubbleCell { // Setting sender name if needed if isGroup && !isOut { - senderNameLabel.hidden = false + senderNameLabel.isHidden = false senderNameLabel.textLayout = self.cellLayout.senderLayout } else { - senderNameLabel.hidden = true + senderNameLabel.isHidden = true senderNameLabel.textLayout = nil } } // Always update bubble insets if (isOut) { - bindBubbleType(.TextOut, isCompact: isClanchBottom) + bindBubbleType(.textOut, isCompact: isClanchBottom) bubbleInsets = UIEdgeInsets( top: (isClanchTop ? AABubbleCell.bubbleTopCompact : AABubbleCell.bubbleTop), @@ -132,7 +132,7 @@ public class AABubbleTextCell : AABubbleCell { bottom: AABubbleCell.bubbleContentBottom, right: (isClanchBottom ? 4 : 10)) } else { - bindBubbleType(.TextIn, isCompact: isClanchBottom) + bindBubbleType(.textIn, isCompact: isClanchBottom) // dateText.textColor = appStyle.chatTextDateInColor bubbleInsets = UIEdgeInsets( @@ -182,45 +182,45 @@ public class AABubbleTextCell : AABubbleCell { // Menu for Text cell - public override func canPerformAction(action: Selector, withSender sender: AnyObject?) -> Bool { - if action == #selector(NSObject.copy(_:)) { - if (bindedMessage!.content is ACTextContent) { - return true - } - } - if action == #selector(NSObject.delete(_:)) { - return true - } - return false - } - - public override func copy(sender: AnyObject?) { - UIPasteboard.generalPasteboard().string = (bindedMessage!.content as! ACTextContent).text - } +// open override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool { +// if action == #selector(NSObject.copy(_:)) { +// if (bindedMessage!.content is ACTextContent) { +// return true +// } +// } +// if action == #selector(NSObject.delete(_:)) { +// return true +// } +// return false +// } +// +// open override func copy(_ sender: AnyObject?) { +// UIPasteboard.general.string = (bindedMessage!.content as! ACTextContent).text +// } - public func urlLongTap(url: NSURL) { + open func urlLongTap(_ url: URL) { if url.scheme != "source" && url.scheme == "send" { - let actionSheet: UIAlertController = UIAlertController(title: nil, message: url.absoluteString, preferredStyle: .ActionSheet) - actionSheet.addAction(UIAlertAction(title: AALocalized("ActionOpenLink"), style: .Default, handler: { action in + let actionSheet: UIAlertController = UIAlertController(title: nil, message: url.absoluteString, preferredStyle: .actionSheet) + actionSheet.addAction(UIAlertAction(title: AALocalized("ActionOpenLink"), style: .default, handler: { action in self.openUrl(url) })) - actionSheet.addAction(UIAlertAction(title: AALocalized("ActionCopyLink"), style: .Default, handler: { action in - UIPasteboard.generalPasteboard().string = url.absoluteString + actionSheet.addAction(UIAlertAction(title: AALocalized("ActionCopyLink"), style: .default, handler: { action in + UIPasteboard.general.string = url.absoluteString self.controller.alertUser("AlertLinkCopied") })) - actionSheet.addAction(UIAlertAction(title: AALocalized("ActionCancel"), style: .Cancel, handler:nil)) - self.controller.presentViewController(actionSheet, animated: true, completion: nil) + actionSheet.addAction(UIAlertAction(title: AALocalized("ActionCancel"), style: .cancel, handler:nil)) + self.controller.present(actionSheet, animated: true, completion: nil) } } - public func openUrl(url: NSURL) { + open func openUrl(_ url: URL) { if url.scheme == "source" { - let path = url.path! - let index = Int(path.substringFromIndex(path.startIndex.advancedBy(1)))! + let path = url.path + let index = Int(path.substring(from: path.characters.index(path.startIndex, offsetBy: 1)))! let code = self.cellLayout.sources[index] self.controller.navigateNext(AACodePreviewController(code: code), removeCurrent: false) } else if url.scheme == "send" { - Actor.sendMessageWithPeer(self.peer, withText: url.absoluteString.skip(5)) + Actor.sendMessage(with: self.peer, withText: url.absoluteString.skip(5)) } else { ActorSDK.sharedActor().openUrl(url.absoluteString) } @@ -228,7 +228,7 @@ public class AABubbleTextCell : AABubbleCell { // Layouting - public override func layoutContent(maxWidth: CGFloat, offsetX: CGFloat) { + open override func layoutContent(_ maxWidth: CGFloat, offsetX: CGFloat) { // Convenience let insets = fullContentInsets @@ -237,18 +237,18 @@ public class AABubbleTextCell : AABubbleCell { let bubbleWidth = round(self.cellLayout.bubbleSize.width) let bubbleHeight = round(self.cellLayout.bubbleSize.height) - self.messageText.frame = CGRectMake(0, 0, textSize.width, textSize.height) + self.messageText.frame = CGRect(x: 0, y: 0, width: textSize.width, height: textSize.height) // Layout elements if (self.isOut) { self.messageText.frame.origin = CGPoint(x: contentWidth - bubbleWidth - insets.right, y: insets.top /*+ topPadding*/) - self.dateText.frame = CGRectMake(contentWidth - insets.right - 70 + 46 - dateWidth, bubbleHeight + insets.top - 20, dateWidth, 26) - self.statusView.frame = CGRectMake(contentWidth - insets.right - 24, bubbleHeight + insets.top - 20, 20, 26) - self.statusView.hidden = false + self.dateText.frame = CGRect(x: contentWidth - insets.right - 70 + 46 - dateWidth, y: bubbleHeight + insets.top - 20, width: dateWidth, height: 26) + self.statusView.frame = CGRect(x: contentWidth - insets.right - 24, y: bubbleHeight + insets.top - 20, width: 20, height: 26) + self.statusView.isHidden = false } else { self.messageText.frame.origin = CGPoint(x: insets.left, y: insets.top/* + topPadding*/) - self.dateText.frame = CGRectMake(insets.left + bubbleWidth - 47 + 46 - dateWidth, bubbleHeight + insets.top - 20, dateWidth, 26) - self.statusView.hidden = true + self.dateText.frame = CGRect(x: insets.left + bubbleWidth - 47 + 46 - dateWidth, y: bubbleHeight + insets.top - 20, width: dateWidth, height: 26) + self.statusView.isHidden = true } if self.isGroup && !self.isOut { @@ -262,25 +262,25 @@ public class AABubbleTextCell : AABubbleCell { /** Text cell layout */ -public class TextCellLayout: AACellLayout { +open class TextCellLayout: AACellLayout { - private class func maxTextWidth(isOut: Bool, peer: ACPeer) -> CGFloat { + fileprivate class func maxTextWidth(_ isOut: Bool, peer: ACPeer) -> CGFloat { if AADevice.isiPad { return 400 } else { if peer.isGroup { if isOut { - return UIScreen.mainScreen().bounds.width - 110 + return UIScreen.main.bounds.width - 110 } else { - return UIScreen.mainScreen().bounds.width - 90 + return UIScreen.main.bounds.width - 90 } } else { - return UIScreen.mainScreen().bounds.width - 40 + return UIScreen.main.bounds.width - 40 } } } - private class func timeWidth(isOut: Bool) -> CGFloat { + fileprivate class func timeWidth(_ isOut: Bool) -> CGFloat { if isOut { return 60 } else { @@ -288,12 +288,12 @@ public class TextCellLayout: AACellLayout { } } - private static let textKey = "text" - private static let unsupportedKey = "unsupported" + fileprivate static let textKey = "text" + fileprivate static let unsupportedKey = "unsupported" - private static let stringOutPadding = " " + ("_".repeatString(7)); - private static let stringInPadding = " " + ("_".repeatString(4)); - private static let parser = ARMarkdownParser(int: ARMarkdownParser_MODE_FULL) + fileprivate static let stringOutPadding = " " + ("_".repeatString(7)); + fileprivate static let stringInPadding = " " + ("_".repeatString(4)); + fileprivate static let parser = ARMarkdownParser(int: ARMarkdownParser_MODE_FULL) var text: String var attrText: NSAttributedString @@ -320,7 +320,7 @@ public class TextCellLayout: AACellLayout { let maxTextWidth = TextCellLayout.maxTextWidth(isOut, peer: peer) let timeWidth = TextCellLayout.timeWidth(isOut) - let container = YYTextContainer(size: CGSizeMake(maxTextWidth, CGFloat.max)) + let container = YYTextContainer(size: CGSize(width: maxTextWidth, height: CGFloat.greatestFiniteMagnitude)) textLayout = YYTextLayout(container: container, text: attributedText)! @@ -380,13 +380,13 @@ public class TextCellLayout: AACellLayout { let attrDate = NSMutableAttributedString(string: AACellLayout.formatDate(date)) attrDate.yy_font = AABubbleTextCell.dateFont attrDate.yy_color = ActorSDK.sharedActor().style.chatTextDateOutColor - dateLayout = YYTextLayout(containerSize: CGSizeMake(timeWidth, CGFloat.max), text: attrDate) + dateLayout = YYTextLayout(containerSize: CGSize(width: timeWidth, height: CGFloat.greatestFiniteMagnitude), text: attrDate) dateWidth = dateLayout?.textBoundingSize.width } else { let attrDate = NSMutableAttributedString(string: AACellLayout.formatDate(date)) attrDate.yy_font = AABubbleTextCell.dateFont attrDate.yy_color = ActorSDK.sharedActor().style.chatTextDateInColor - dateLayout = YYTextLayout(containerSize: CGSizeMake(timeWidth, CGFloat.max), text: attrDate) + dateLayout = YYTextLayout(containerSize: CGSize(width: timeWidth, height: CGFloat.greatestFiniteMagnitude), text: attrDate) dateWidth = dateLayout?.textBoundingSize.width } @@ -485,17 +485,17 @@ public class TextCellLayout: AACellLayout { /** Text cell layouter */ -public class AABubbleTextCellLayouter: AABubbleLayouter { +open class AABubbleTextCellLayouter: AABubbleLayouter { - public func buildLayout(peer: ACPeer, message: ACMessage) -> AACellLayout { + open func buildLayout(_ peer: ACPeer, message: ACMessage) -> AACellLayout { return TextCellLayout(message: message, peer: peer, layouter: self) } - public func isSuitable(message: ACMessage) -> Bool { + open func isSuitable(_ message: ACMessage) -> Bool { return message.content is ACTextContent } - public func cellClass() -> AnyClass { + open func cellClass() -> AnyClass { return AABubbleTextCell.self } } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Cell/AABubbleVoiceCell.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Cell/AABubbleVoiceCell.swift index 2c0318ca6d..0408ce3609 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Cell/AABubbleVoiceCell.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Cell/AABubbleVoiceCell.swift @@ -6,20 +6,20 @@ import UIKit import VBFPopFlatButton import YYImage -public class AABubbleVoiceCell: AABubbleBaseFileCell,AAModernConversationAudioPlayerDelegate,AAModernViewInlineMediaContextDelegate { +open class AABubbleVoiceCell: AABubbleBaseFileCell,AAModernConversationAudioPlayerDelegate,AAModernViewInlineMediaContextDelegate { // Views - private let progress = AAProgressView(size: CGSizeMake(44, 44)) - private let timeLabel = AttributedLabel() - private let voiceTimeLabel = AttributedLabel() - private let playPauseButton = UIButton() - private let soundProgressSlider = UISlider() - private let statusView = UIImageView() + fileprivate let progress = AAProgressView(size: CGSize(width: 44, height: 44)) + fileprivate let timeLabel = AttributedLabel() + fileprivate let voiceTimeLabel = AttributedLabel() + fileprivate let playPauseButton = UIButton() + fileprivate let soundProgressSlider = UISlider() + fileprivate let statusView = UIImageView() - private let durationLabel = AttributedLabel() - private let titleLabel = AttributedLabel() + fileprivate let durationLabel = AttributedLabel() + fileprivate let titleLabel = AttributedLabel() // Binded data @@ -35,42 +35,42 @@ public class AABubbleVoiceCell: AABubbleBaseFileCell,AAModernConversationAudioPl //////////////////////////////////////////////////// - timeLabel.font = UIFont.italicSystemFontOfSize(11) + timeLabel.font = UIFont.italicSystemFont(ofSize: 11) timeLabel.textColor = appStyle.chatTextDateOutColor //////////////////////////////////////////////////// - statusView.contentMode = UIViewContentMode.Center + statusView.contentMode = UIViewContentMode.center //////////////////////////////////////////////////// soundProgressSlider.tintColor = appStyle.chatStatusSending - soundProgressSlider.userInteractionEnabled = false + soundProgressSlider.isUserInteractionEnabled = false //soundProgressSlider.addTarget(self, action: "seekToNewAudioValue", forControlEvents: UIControlEvents.ValueChanged) let insets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) let trackLeftImage = UIImage.tinted("aa_voiceplaybackground", color: UIColor(red: 0.0, green: 0.761, blue: 0.9964, alpha: 1.0 )) - let trackLeftResizable = trackLeftImage.resizableImageWithCapInsets(insets) - soundProgressSlider.setMinimumTrackImage(trackLeftResizable, forState: .Normal) + let trackLeftResizable = trackLeftImage.resizableImage(withCapInsets: insets) + soundProgressSlider.setMinimumTrackImage(trackLeftResizable, for: UIControlState()) let trackRightImage = UIImage.tinted("aa_voiceplaybackground", color: UIColor(red: 0.0, green: 0.5856, blue: 0.9985, alpha: 1.0 )) - let trackRightResizable = trackRightImage.resizableImageWithCapInsets(insets) - soundProgressSlider.setMaximumTrackImage(trackRightResizable, forState: .Normal) + let trackRightResizable = trackRightImage.resizableImage(withCapInsets: insets) + soundProgressSlider.setMaximumTrackImage(trackRightResizable, for: UIControlState()) let thumbImageNormal = UIImage.bundled("aa_thumbvoiceslider") - soundProgressSlider.setThumbImage(thumbImageNormal, forState: .Normal) + soundProgressSlider.setThumbImage(thumbImageNormal, for: UIControlState()) //////////////////////////////////////////////////// - voiceTimeLabel.font = UIFont.italicSystemFontOfSize(11) + voiceTimeLabel.font = UIFont.italicSystemFont(ofSize: 11) voiceTimeLabel.textColor = appStyle.chatTextDateOutColor //////////////////////////////////////////////////// - durationLabel.font = UIFont.systemFontOfSize(11.0) + durationLabel.font = UIFont.systemFont(ofSize: 11.0) durationLabel.textColor = appStyle.chatTextDateOutColor durationLabel.text = " " durationLabel.sizeToFit() @@ -97,8 +97,8 @@ public class AABubbleVoiceCell: AABubbleBaseFileCell,AAModernConversationAudioPl //////////////////////////////////////////////////// - playPauseButton.setImage(UIImage.bundled("aa_playrecordbutton"), forState: UIControlState.Normal) - playPauseButton.addTarget(self, action: #selector(AABubbleVoiceCell.mediaDidTap), forControlEvents: UIControlEvents.TouchUpInside) + playPauseButton.setImage(UIImage.bundled("aa_playrecordbutton"), for: UIControlState()) + playPauseButton.addTarget(self, action: #selector(AABubbleVoiceCell.mediaDidTap), for: UIControlEvents.touchUpInside) contentInsets = UIEdgeInsets(top: 1, left: 1, bottom: 1, right: 1) @@ -110,7 +110,7 @@ public class AABubbleVoiceCell: AABubbleBaseFileCell,AAModernConversationAudioPl // MARK: - Binding - public override func bind(message: ACMessage, receiveDate: jlong, readDate: jlong, reuse: Bool, cellLayout: AACellLayout, setting: AACellSetting) { + open override func bind(_ message: ACMessage, receiveDate: jlong, readDate: jlong, reuse: Bool, cellLayout: AACellLayout, setting: AACellSetting) { self.bindedLayout = cellLayout as! VoiceMessageCellLayout let document = message.content as! ACVoiceContent @@ -126,9 +126,9 @@ public class AABubbleVoiceCell: AABubbleBaseFileCell,AAModernConversationAudioPl // Bind bubble if (self.isOut) { - bindBubbleType(BubbleType.MediaOut, isCompact: false) + bindBubbleType(BubbleType.mediaOut, isCompact: false) } else { - bindBubbleType(BubbleType.MediaIn, isCompact: false) + bindBubbleType(BubbleType.mediaIn, isCompact: false) } titleLabel.text = AALocalized("ChatVoiceMessage") @@ -136,8 +136,8 @@ public class AABubbleVoiceCell: AABubbleBaseFileCell,AAModernConversationAudioPl // Reset progress self.progress.hideButton() - UIView.animateWithDuration(0, animations: { () -> Void in - self.progress.hidden = true + UIView.animate(withDuration: 0, animations: { () -> Void in + self.progress.isHidden = true }) // Bind file @@ -162,16 +162,16 @@ public class AABubbleVoiceCell: AABubbleBaseFileCell,AAModernConversationAudioPl if self.controller.currentAudioFileId != fileID { self.soundProgressSlider.value = 0.0 - self.playPauseButton.setImage(UIImage.bundled("aa_playrecordbutton"), forState: UIControlState.Normal) + self.playPauseButton.setImage(UIImage.bundled("aa_playrecordbutton"), for: UIControlState()) self.controller.voiceContext?.removeDelegate(self) } else { self.controller.voiceContext?.delegate = self self.controller.voicePlayer?.delegate = self if self.controller.voicePlayer?.isPaused() == false { - self.playPauseButton.setImage(UIImage.bundled("aa_pauserecordbutton"), forState: UIControlState.Normal) + self.playPauseButton.setImage(UIImage.bundled("aa_pauserecordbutton"), for: UIControlState()) } else { - self.playPauseButton.setImage(UIImage.bundled("aa_playrecordbutton"), forState: UIControlState.Normal) + self.playPauseButton.setImage(UIImage.bundled("aa_playrecordbutton"), for: UIControlState()) } } @@ -192,7 +192,7 @@ public class AABubbleVoiceCell: AABubbleBaseFileCell,AAModernConversationAudioPl // Update status if (isOut) { - statusView.hidden = false + statusView.isHidden = false switch(message.messageState.toNSEnum()) { case .SENT: if message.sortDate <= readDate { @@ -215,14 +215,14 @@ public class AABubbleVoiceCell: AABubbleBaseFileCell,AAModernConversationAudioPl break } } else { - statusView.hidden = true + statusView.isHidden = true } } //MARK: - File state binding - public override func fileStateChanged(reference: String?, progress: Int?, isPaused: Bool, isUploading: Bool, selfGeneration: Int) { + open override func fileStateChanged(_ reference: String?, progress: Int?, isPaused: Bool, isUploading: Bool, selfGeneration: Int) { runOnUiThread(selfGeneration) { () -> () in if isUploading { if isPaused { @@ -263,14 +263,14 @@ public class AABubbleVoiceCell: AABubbleBaseFileCell,AAModernConversationAudioPl //MARK: - Media Action - public func mediaDidTap() { + open func mediaDidTap() { let content = bindedMessage!.content as! ACDocumentContent if let fileSource = content.getSource() as? ACFileRemoteSource { - Actor.requestStateWithFileId(fileSource.getFileReference().getFileId(), withCallback: AAFileCallback( + Actor.requestState(withFileId: fileSource.getFileReference().getFileId(), with: AAFileCallback( notDownloaded: { () -> () in - Actor.startDownloadingWithReference(fileSource.getFileReference()) + Actor.startDownloading(with: fileSource.getFileReference()) }, onDownloading: { (progress) -> () in - Actor.cancelDownloadingWithFileId(fileSource.getFileReference().getFileId()) + Actor.cancelDownloading(withFileId: fileSource.getFileReference().getFileId()) }, onDownloaded: { (reference) -> () in dispatchOnUi({ () -> Void in @@ -281,7 +281,7 @@ public class AABubbleVoiceCell: AABubbleBaseFileCell,AAModernConversationAudioPl self.controller.playVoiceFromPath(path,fileId: fileID,position:self.soundProgressSlider.value) - self.playPauseButton.setImage(UIImage.bundled("aa_pauserecordbutton"), forState: UIControlState.Normal) + self.playPauseButton.setImage(UIImage.bundled("aa_pauserecordbutton"), for: UIControlState()) self.controller.voicePlayer.delegate = self self.controller.voiceContext.delegate = self @@ -292,11 +292,11 @@ public class AABubbleVoiceCell: AABubbleBaseFileCell,AAModernConversationAudioPl } else if let fileSource = content.getSource() as? ACFileLocalSource { let rid = bindedMessage!.rid - Actor.requestUploadStateWithRid(rid, withCallback: AAUploadFileCallback( + Actor.requestUploadState(withRid: rid, with: AAUploadFileCallback( notUploaded: { () -> () in - Actor.resumeUploadWithRid(rid) + Actor.resumeUpload(withRid: rid) }, onUploading: { (progress) -> () in - Actor.pauseUploadWithRid(rid) + Actor.pauseUpload(withRid: rid) }, onUploadedClosure: { () -> () in dispatchOnUi({ () -> Void in @@ -309,7 +309,7 @@ public class AABubbleVoiceCell: AABubbleBaseFileCell,AAModernConversationAudioPl self.controller.playVoiceFromPath(path,fileId: fileID,position:self.soundProgressSlider.value) - self.playPauseButton.setImage(UIImage.bundled("aa_pauserecordbutton"), forState: UIControlState.Normal) + self.playPauseButton.setImage(UIImage.bundled("aa_pauserecordbutton"), for: UIControlState()) self.controller.voicePlayer.delegate = self self.controller.voiceContext.delegate = self @@ -324,15 +324,15 @@ public class AABubbleVoiceCell: AABubbleBaseFileCell,AAModernConversationAudioPl } } - public func seekToNewAudioValue() { + open func seekToNewAudioValue() { } - public func audioPlayerDidFinish() { + open func audioPlayerDidFinish() { dispatchOnUi { () -> Void in - self.playPauseButton.setImage(UIImage.bundled("aa_playrecordbutton"), forState: UIControlState.Normal) + self.playPauseButton.setImage(UIImage.bundled("aa_playrecordbutton"), for: UIControlState()) self.soundProgressSlider.value = 0.0 self.controller.voicesCache[self.controller.currentAudioFileId] = 0.0 @@ -340,14 +340,14 @@ public class AABubbleVoiceCell: AABubbleBaseFileCell,AAModernConversationAudioPl } - public func inlineMediaPlaybackStateUpdated(isPaused: Bool, playbackPosition: Float, timestamp: NSTimeInterval, preciseDuration: NSTimeInterval) { + open func inlineMediaPlaybackStateUpdated(_ isPaused: Bool, playbackPosition: Float, timestamp: TimeInterval, preciseDuration: TimeInterval) { dispatchOnUi({ () -> Void in self.soundProgressSlider.value = playbackPosition if (isPaused == true) { - self.playPauseButton.setImage(UIImage.bundled("aa_playrecordbutton"), forState: UIControlState.Normal) + self.playPauseButton.setImage(UIImage.bundled("aa_playrecordbutton"), for: UIControlState()) } self.controller.voicesCache[self.controller.currentAudioFileId] = playbackPosition @@ -359,7 +359,7 @@ public class AABubbleVoiceCell: AABubbleBaseFileCell,AAModernConversationAudioPl //MARK: - Layouting - public override func layoutContent(maxWidth: CGFloat, offsetX: CGFloat) { + open override func layoutContent(_ maxWidth: CGFloat, offsetX: CGFloat) { let insets = fullContentInsets let contentWidth = self.contentView.frame.width @@ -370,25 +370,25 @@ public class AABubbleVoiceCell: AABubbleBaseFileCell,AAModernConversationAudioPl let top = insets.top - 2 // Progress state - let progressRect = CGRectMake(contentLeft + 7.5, 7.5 + top, 44, 44) + let progressRect = CGRect(x: contentLeft + 7.5, y: 7.5 + top, width: 44, height: 44) self.progress.frame = progressRect self.playPauseButton.frame = progressRect - timeLabel.frame = CGRectMake(0, 0, 1000, 1000) + timeLabel.frame = CGRect(x: 0, y: 0, width: 1000, height: 1000) timeLabel.sizeToFit() // Content - self.soundProgressSlider.frame = CGRectMake(contentLeft + 62, 16 + top, 200 - 70, 22) - self.durationLabel.frame = CGRectMake(contentLeft + 62, 10 + 25 + top, 200 - 64, 22) + self.soundProgressSlider.frame = CGRect(x: contentLeft + 62, y: 16 + top, width: 200 - 70, height: 22) + self.durationLabel.frame = CGRect(x: contentLeft + 62, y: 10 + 25 + top, width: 200 - 64, height: 22) // Message state if (self.isOut) { - self.timeLabel.frame = CGRectMake(self.bubble.frame.maxX - 55 - self.bubblePadding, self.bubble.frame.maxY - 24, 46, 26) - self.statusView.frame = CGRectMake(self.bubble.frame.maxX - 24 - self.bubblePadding, self.bubble.frame.maxY - 24, 20, 26) - self.statusView.hidden = false + self.timeLabel.frame = CGRect(x: self.bubble.frame.maxX - 55 - self.bubblePadding, y: self.bubble.frame.maxY - 24, width: 46, height: 26) + self.statusView.frame = CGRect(x: self.bubble.frame.maxX - 24 - self.bubblePadding, y: self.bubble.frame.maxY - 24, width: 20, height: 26) + self.statusView.isHidden = false } else { - self.timeLabel.frame = CGRectMake(self.bubble.frame.maxX - 47 - self.bubblePadding, self.bubble.frame.maxY - 24, 46, 26) - self.statusView.hidden = true + self.timeLabel.frame = CGRect(x: self.bubble.frame.maxX - 47 - self.bubblePadding, y: self.bubble.frame.maxY - 24, width: 46, height: 26) + self.statusView.isHidden = true } } @@ -398,16 +398,16 @@ public class AABubbleVoiceCell: AABubbleBaseFileCell,AAModernConversationAudioPl /** Voice cell layout */ -public class VoiceMessageCellLayout: AACellLayout { +open class VoiceMessageCellLayout: AACellLayout { - public let contentSize: CGSize - public let screenSize: CGSize - public let autoDownload: Bool + open let contentSize: CGSize + open let screenSize: CGSize + open let autoDownload: Bool - public let fileName: String - public let fileExt: String - public let fileSize: String - public var voiceDuration: String! + open let fileName: String + open let fileExt: String + open let fileSize: String + open var voiceDuration: String! /** Creting layout for media bubble @@ -415,7 +415,7 @@ public class VoiceMessageCellLayout: AACellLayout { public init(fileName: String, fileExt: String, fileSize: Int,id: Int64, date: Int64, autoDownload: Bool,duration:jint, layouter: AABubbleLayouter) { // Saving content size - self.contentSize = CGSizeMake(200, 55) + self.contentSize = CGSize(width: 200, height: 55) // Saving autodownload flag self.autoDownload = autoDownload @@ -424,7 +424,7 @@ public class VoiceMessageCellLayout: AACellLayout { self.screenSize = CGSize(width: 200, height: 55) self.fileName = fileName - self.fileExt = fileExt.lowercaseString + self.fileExt = fileExt.lowercased() self.fileSize = Actor.getFormatter().formatFileSize(jint(fileSize)) // Creating layout @@ -433,7 +433,7 @@ public class VoiceMessageCellLayout: AACellLayout { self.voiceDuration = getTimeString(Int(duration)) } - func getTimeString(totalSeconds:Int) -> String { + func getTimeString(_ totalSeconds:Int) -> String { let seconds = Int(totalSeconds % 60) let minutes = Int((totalSeconds / 60) % 60) @@ -478,9 +478,9 @@ public class VoiceMessageCellLayout: AACellLayout { /** Layouter for voice bubbles */ -public class AABubbleVoiceCellLayouter: AABubbleLayouter { +open class AABubbleVoiceCellLayouter: AABubbleLayouter { - public func isSuitable(message: ACMessage) -> Bool { + open func isSuitable(_ message: ACMessage) -> Bool { if message.content is ACVoiceContent { return true } @@ -488,11 +488,11 @@ public class AABubbleVoiceCellLayouter: AABubbleLayouter { return false } - public func buildLayout(peer: ACPeer, message: ACMessage) -> AACellLayout { + open func buildLayout(_ peer: ACPeer, message: ACMessage) -> AACellLayout { return VoiceMessageCellLayout(message: message, layouter: self) } - public func cellClass() -> AnyClass { + open func cellClass() -> AnyClass { return AABubbleVoiceCell.self } } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Layouting/AABubbleBackgroundProcessor.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Layouting/AABubbleBackgroundProcessor.swift index f4fbcd76c1..d243c0ae05 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Layouting/AABubbleBackgroundProcessor.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Layouting/AABubbleBackgroundProcessor.swift @@ -49,7 +49,7 @@ class AAListProcessor: NSObject, ARListProcessor { self.peer = peer } - func processWithItems(items: JavaUtilList, withPrevious previous: AnyObject?) -> AnyObject? { + func process(withItems items: JavaUtilList, withPrevious previous: Any?) -> Any? { var objs = [ACMessage]() var indexes = [jlong: Int]() @@ -74,7 +74,7 @@ class AAListProcessor: NSObject, ARListProcessor { // Building content list and dictionary from rid to index list for i in 0.. AACellSetting { + func buildCellSetting(_ index: Int, items: [ACMessage]) -> AACellSetting { let current = items[index] let id = Int64(current.rid) @@ -240,14 +240,14 @@ class AAListProcessor: NSObject, ARListProcessor { /** Checking if messages have same send day */ - func areSameDate(source:ACMessage, prev: ACMessage) -> Bool { - let calendar = NSCalendar.currentCalendar() + func areSameDate(_ source:ACMessage, prev: ACMessage) -> Bool { + let calendar = Calendar.current - let currentDate = NSDate(timeIntervalSince1970: Double(source.date)/1000.0) - let currentDateComp = calendar.components([.Day, .Year, .Month], fromDate: currentDate) + let currentDate = Date(timeIntervalSince1970: Double(source.date)/1000.0) + let currentDateComp = (calendar as NSCalendar).components([.day, .year, .month], from: currentDate) - let nextDate = NSDate(timeIntervalSince1970: Double(prev.date)/1000.0) - let nextDateComp = calendar.components([.Day, .Year, .Month], fromDate: nextDate) + let nextDate = Date(timeIntervalSince1970: Double(prev.date)/1000.0) + let nextDateComp = (calendar as NSCalendar).components([.day, .year, .month], from: nextDate) return (currentDateComp.year == nextDateComp.year && currentDateComp.month == nextDateComp.month && currentDateComp.day == nextDateComp.day) } @@ -255,7 +255,7 @@ class AAListProcessor: NSObject, ARListProcessor { /** Checking if it is good to make bubbles clenched */ - func useCompact(source: ACMessage, next: ACMessage) -> Bool { + func useCompact(_ source: ACMessage, next: ACMessage) -> Bool { if (source.content is ACServiceContent) { if (next.content is ACServiceContent) { return true @@ -272,7 +272,7 @@ class AAListProcessor: NSObject, ARListProcessor { return false } - func measureHeight(message: ACMessage, setting: AACellSetting, layout: AACellLayout) -> CGFloat { + func measureHeight(_ message: ACMessage, setting: AACellSetting, layout: AACellLayout) -> CGFloat { let content = message.content! @@ -293,7 +293,7 @@ class AAListProcessor: NSObject, ARListProcessor { return height } - func buildLayout(message: ACMessage, layoutCache: AALayoutCache) -> AACellLayout { + func buildLayout(_ message: ACMessage, layoutCache: AALayoutCache) -> AACellLayout { var layout: AACellLayout! = layoutCache.pick(message.rid) @@ -305,4 +305,4 @@ class AAListProcessor: NSObject, ARListProcessor { return layout } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Layouting/AAMessagesFlowLayout.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Layouting/AAMessagesFlowLayout.swift index e1d411ea4c..1dc0a6cc23 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Layouting/AAMessagesFlowLayout.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Layouting/AAMessagesFlowLayout.swift @@ -8,8 +8,8 @@ private let ENABLE_LOGS = false class AAMessagesFlowLayout : UICollectionViewLayout { - var deletedIndexPaths = [NSIndexPath]() - var insertedIndexPaths = [NSIndexPath]() + var deletedIndexPaths = [IndexPath]() + var insertedIndexPaths = [IndexPath]() var items = [AALayoutItem?]() var frames = [CGRect?]() var isLoadMore: Bool = false @@ -29,18 +29,18 @@ class AAMessagesFlowLayout : UICollectionViewLayout { fatalError("init(coder:) has not been implemented") } - func beginUpdates(isLoadMore: Bool, list: AAPreprocessedList?, unread: jlong?) { + func beginUpdates(_ isLoadMore: Bool, list: AAPreprocessedList?, unread: jlong?) { self.isLoadMore = isLoadMore self.list = list self.unread = unread // Saving current visible cells - currentItems.removeAll(keepCapacity: true) - let visibleItems = self.collectionView!.indexPathsForVisibleItems() + currentItems.removeAll(keepingCapacity: true) + let visibleItems = self.collectionView!.indexPathsForVisibleItems let currentOffset = self.collectionView!.contentOffset.y for indexPath in visibleItems { - let index = indexPath.item + let index = (indexPath as NSIndexPath).item let topOffset = items[index]!.attrs.frame.origin.y - currentOffset let id = items[index]!.id currentItems.append(AACachedLayout(id: id, offset: topOffset)) @@ -49,15 +49,15 @@ class AAMessagesFlowLayout : UICollectionViewLayout { isScrolledToEnd = self.collectionView!.contentOffset.y < 8 } - override func prepareLayout() { - super.prepareLayout() + override func prepare() { + super.prepare() autoreleasepool { // Validate sections - let sectionsCount = self.collectionView!.numberOfSections() + let sectionsCount = self.collectionView!.numberOfSections if sectionsCount == 0 { - items.removeAll(keepCapacity: false) + items.removeAll(keepingCapacity: false) contentHeight = 0.0 return } @@ -72,8 +72,8 @@ class AAMessagesFlowLayout : UICollectionViewLayout { } // items.removeAll(keepCapacity: false) // frames.removeAll(keepCapacity: false) - items.removeAll(keepCapacity: true) - frames.removeAll(keepCapacity: true) + items.removeAll(keepingCapacity: true) + frames.removeAll(keepingCapacity: true) if list != nil { @@ -86,14 +86,14 @@ class AAMessagesFlowLayout : UICollectionViewLayout { if itemId == unread { height += AABubbleCell.newMessageSize } - let itemSize = CGSizeMake(self.collectionView!.bounds.width, height) + let itemSize = CGSize(width: self.collectionView!.bounds.width, height: height) - let frame = CGRect(origin: CGPointMake(0, contentHeight), size: itemSize) + let frame = CGRect(origin: CGPoint(x: 0, y: contentHeight), size: itemSize) var item = AALayoutItem(id: itemId) item.size = itemSize - let attrs = UICollectionViewLayoutAttributes(forCellWithIndexPath: NSIndexPath(forRow: i, inSection: 0)) + let attrs = UICollectionViewLayoutAttributes(forCellWith: IndexPath(row: i, section: 0)) attrs.frame = frame item.attrs = attrs @@ -115,33 +115,33 @@ class AAMessagesFlowLayout : UICollectionViewLayout { } } - override func collectionViewContentSize() -> CGSize { + override var collectionViewContentSize : CGSize { return CGSize(width: self.collectionView!.bounds.width, height: contentHeight) } - override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? { + override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { var res = [UICollectionViewLayoutAttributes]() for i in 0.. UICollectionViewLayoutAttributes? { - return items[indexPath.item]!.attrs + override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? { + return items[(indexPath as NSIndexPath).item]!.attrs } - override func initialLayoutAttributesForAppearingItemAtIndexPath(itemIndexPath: NSIndexPath) -> UICollectionViewLayoutAttributes? { - let res = super.initialLayoutAttributesForAppearingItemAtIndexPath(itemIndexPath) + override func initialLayoutAttributesForAppearingItem(at itemIndexPath: IndexPath) -> UICollectionViewLayoutAttributes? { + let res = super.initialLayoutAttributesForAppearingItem(at: itemIndexPath) if !isLoadMore && insertedIndexPaths.contains(itemIndexPath) { res?.alpha = 0 - res?.transform = CGAffineTransformTranslate(CGAffineTransformIdentity, 0, -44) + res?.transform = CGAffineTransform.identity.translatedBy(x: 0, y: -44) } else { res?.alpha = 1 } @@ -184,7 +184,7 @@ class AAMessagesFlowLayout : UICollectionViewLayout { } if delta != nil { - self.collectionView!.contentOffset = CGPointMake(0, self.collectionView!.contentOffset.y - delta) + self.collectionView!.contentOffset = CGPoint(x: 0, y: self.collectionView!.contentOffset.y - delta) } } @@ -215,7 +215,7 @@ struct AACachedLayout { } @objc enum AAMessageGravity: Int { - case Left - case Right - case Center + case left + case right + case center } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Layouting/BubbleLayouts.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Layouting/BubbleLayouts.swift index 176ea727d8..1419814c2c 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Layouting/BubbleLayouts.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Layouting/BubbleLayouts.swift @@ -21,9 +21,9 @@ public func ==(lhs: AACellSetting, rhs: AACellSetting) -> Bool { return lhs.showDate == rhs.showDate && lhs.clenchTop == rhs.clenchTop && lhs.clenchBottom == rhs.clenchBottom } -private let dateFormatter = NSDateFormatter().initDateFormatter() +private let dateFormatter = DateFormatter().initDateFormatter() -public class AACellLayout { +open class AACellLayout { let layouter: AABubbleLayouter let key: String @@ -39,11 +39,11 @@ public class AACellLayout { self.layouter = layouter } - public class func formatDate(date: Int64) -> String { - return dateFormatter.stringFromDate(NSDate(timeIntervalSince1970: NSTimeInterval(Double(date) / 1000.0))) + open class func formatDate(_ date: Int64) -> String { + return dateFormatter.string(from: Date(timeIntervalSince1970: TimeInterval(Double(date) / 1000.0))) } - public class func pickApproriateSize(width: CGFloat, height: CGFloat) -> CGSize { + open class func pickApproriateSize(_ width: CGFloat, height: CGFloat) -> CGSize { let maxW: CGFloat let maxH: CGFloat if AADevice.isiPad { @@ -65,8 +65,8 @@ public class AACellLayout { } } -extension NSDateFormatter { - func initDateFormatter() -> NSDateFormatter { +extension DateFormatter { + func initDateFormatter() -> DateFormatter { dateFormat = "HH:mm" return self } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Layouting/Caches.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Layouting/Caches.swift index 13da981b2b..f593930853 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Layouting/Caches.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Layouting/Caches.swift @@ -5,17 +5,17 @@ import Foundation class AACache { - private var cache = AAHashMap() + fileprivate var cache = AAHashMap() - func pick(id: Int64) -> T? { + func pick(_ id: Int64) -> T? { return cache.getValueAtKey(id) } - func cache(id: Int64, value: T) { + func cache(_ id: Int64, value: T) { cache.setKey(id, withValue: value) } - func revoke(id: Int64) { + func revoke(_ id: Int64) { cache.setKey(id, withValue: nil) } @@ -33,7 +33,7 @@ struct AACachedSetting { self.cached = cached } - func isValid(prev: ACMessage?, next:ACMessage?) -> Bool { + func isValid(_ prev: ACMessage?, next:ACMessage?) -> Bool { if prev?.rid != prevId { return false } @@ -51,13 +51,13 @@ class AALayoutCache : AACache { } class AAFastThumbCache { - private var thumbs = AAHashMap() + fileprivate var thumbs = AAHashMap() - func pick(id: Int64) -> UIImage? { + func pick(_ id: Int64) -> UIImage? { return thumbs.getValueAtKey(id) } - func cache(id: Int64, image: UIImage) { + func cache(_ id: Int64, image: UIImage) { thumbs.setKey(id, withValue: image) } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Suggestion/AAAutoCompleteCell.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Suggestion/AAAutoCompleteCell.swift index 4dd6010e5c..663f6db4bd 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Suggestion/AAAutoCompleteCell.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Suggestion/AAAutoCompleteCell.swift @@ -8,17 +8,17 @@ import TTTAttributedLabel class AAAutoCompleteCell: AATableViewCell { var avatarView = AAAvatarView() - var nickView = TTTAttributedLabel(frame: CGRectZero) - var nameView = TTTAttributedLabel(frame: CGRectZero) + var nickView = TTTAttributedLabel(frame: CGRect.zero) + var nameView = TTTAttributedLabel(frame: CGRect.zero) override init(style: UITableViewCellStyle, reuseIdentifier: String?) { - super.init(style: UITableViewCellStyle.Default, reuseIdentifier: reuseIdentifier) + super.init(style: UITableViewCellStyle.default, reuseIdentifier: reuseIdentifier) // avatarView.enableAnimation = false - nickView.font = UIFont.systemFontOfSize(14) + nickView.font = UIFont.systemFont(ofSize: 14) nickView.textColor = ActorSDK.sharedActor().style.cellTextColor - nameView.font = UIFont.systemFontOfSize(14) + nameView.font = UIFont.systemFont(ofSize: 14) nameView.textColor = ActorSDK.sharedActor().style.cellHintColor self.contentView.addSubview(avatarView) @@ -30,7 +30,7 @@ class AAAutoCompleteCell: AATableViewCell { fatalError("init(coder:) has not been implemented") } - func bindData(user: ACMentionFilterResult, highlightWord: String) { + func bindData(_ user: ACMentionFilterResult, highlightWord: String) { avatarView.bind(user.mentionString, id: Int(user.uid), avatar: user.avatar) @@ -47,21 +47,21 @@ class AAAutoCompleteCell: AATableViewCell { let nickAttrs = NSMutableAttributedString(string: nickText) let nameAttrs = NSMutableAttributedString(string: nameText) - nickAttrs.addAttribute(NSFontAttributeName, value: UIFont.systemFontOfSize(14), range: NSMakeRange(0, nickText.length)) + nickAttrs.addAttribute(NSFontAttributeName, value: UIFont.systemFont(ofSize: 14), range: NSMakeRange(0, nickText.length)) nickAttrs.addAttribute(NSForegroundColorAttributeName, value: ActorSDK.sharedActor().style.cellTextColor, range: NSMakeRange(0, nickText.length)) - nameAttrs.addAttribute(NSFontAttributeName, value: UIFont.systemFontOfSize(14), range: NSMakeRange(0, nameText.length)) + nameAttrs.addAttribute(NSFontAttributeName, value: UIFont.systemFont(ofSize: 14), range: NSMakeRange(0, nameText.length)) nameAttrs.addAttribute(NSForegroundColorAttributeName, value: ActorSDK.sharedActor().style.cellHintColor, range: NSMakeRange(0, nameText.length)) for i in 0.. Void in - UIView.animateWithDuration(0.4, delay: 0.0, usingSpringWithDamping: 0.7, - initialSpringVelocity: 0.6, options: .CurveEaseInOut, animations: { - self.sheetView.frame = CGRectMake(0, self.frame.height - self.sheetViewHeight, self.frame.width, self.sheetViewHeight) + UIView.animate(withDuration: 0.4, delay: 0.0, usingSpringWithDamping: 0.7, + initialSpringVelocity: 0.6, options: UIViewAnimationOptions(), animations: { + self.sheetView.frame = CGRect(x: 0, y: self.frame.height - self.sheetViewHeight, width: self.frame.width, height: self.sheetViewHeight) self.backgroundView.alpha = 1 }, completion: nil) } } - public func dismiss() { + open func dismiss() { var nextFrame = self.sheetView.frame nextFrame.origin.y = self.presentedInController.view.height if let navigation = presentedInController as? UINavigationController { - navigation.interactivePopGestureRecognizer?.enabled = true + navigation.interactivePopGestureRecognizer?.isEnabled = true } else if let navigation = presentedInController.navigationController { - navigation.interactivePopGestureRecognizer?.enabled = true + navigation.interactivePopGestureRecognizer?.isEnabled = true } - UIView.animateWithDuration(0.25, animations: { () -> Void in + UIView.animate(withDuration: 0.25, animations: { () -> Void in self.sheetView.frame = nextFrame - self.backgroundView.alpha = 0}) { (bool) -> Void in + self.backgroundView.alpha = 0}, completion: { (bool) -> Void in self.delegate = nil if self.thumbnailView != nil { self.thumbnailView.dismiss() self.thumbnailView = nil } self.removeFromSuperview() - } + }) } - private func setupAllViews() { + fileprivate func setupAllViews() { // @@ -123,26 +123,26 @@ public class AAConvActionSheet: UIView, AAThumbnailViewDelegate { if enablePhotoPicker { - self.thumbnailView = AAThumbnailView(frame: CGRectMake(0, 5, superWidth, 90)) + self.thumbnailView = AAThumbnailView(frame: CGRect(x: 0, y: 5, width: superWidth, height: 90)) self.thumbnailView.delegate = self self.thumbnailView.open() self.btnCamera = { - let button = UIButton(type: UIButtonType.System) + let button = UIButton(type: UIButtonType.system) button.tintColor = UIColor(red: 5.0/255.0, green: 124.0/255.0, blue: 226.0/255.0, alpha: 1) - button.titleLabel?.font = UIFont.systemFontOfSize(17) - button.setTitle(AALocalized("PhotoCamera"), forState: UIControlState.Normal) - button.addTarget(self, action: #selector(AAConvActionSheet.btnCameraAction), forControlEvents: UIControlEvents.TouchUpInside) + button.titleLabel?.font = UIFont.systemFont(ofSize: 17) + button.setTitle(AALocalized("PhotoCamera"), for: UIControlState()) + button.addTarget(self, action: #selector(AAConvActionSheet.btnCameraAction), for: UIControlEvents.touchUpInside) return button }() self.buttons.append(self.btnCamera) self.btnLibrary = { - let button = UIButton(type: UIButtonType.System) + let button = UIButton(type: UIButtonType.system) button.tintColor = UIColor(red: 5.0/255.0, green: 124.0/255.0, blue: 226.0/255.0, alpha: 1) - button.titleLabel?.font = UIFont.systemFontOfSize(17) - button.setTitle(AALocalized("PhotoLibrary"), forState: UIControlState.Normal) - button.addTarget(self, action: #selector(AAConvActionSheet.btnLibraryAction), forControlEvents: UIControlEvents.TouchUpInside) + button.titleLabel?.font = UIFont.systemFont(ofSize: 17) + button.setTitle(AALocalized("PhotoLibrary"), for: UIControlState()) + button.addTarget(self, action: #selector(AAConvActionSheet.btnLibraryAction), for: UIControlEvents.touchUpInside) return button }() self.buttons.append(self.btnLibrary) @@ -153,22 +153,22 @@ public class AAConvActionSheet: UIView, AAThumbnailViewDelegate { for i in 0.. 0 { var sendString:String @@ -226,14 +226,14 @@ public class AAConvActionSheet: UIView, AAThumbnailViewDelegate { // // remove target // - self.btnCamera.removeTarget(self, action: #selector(AAConvActionSheet.btnCameraAction), forControlEvents: UIControlEvents.TouchUpInside) + self.btnCamera.removeTarget(self, action: #selector(AAConvActionSheet.btnCameraAction), for: UIControlEvents.touchUpInside) // // add new target // - self.btnCamera.setTitle(sendString, forState: UIControlState.Normal) - self.btnCamera.addTarget(self, action:#selector(AAConvActionSheet.sendPhotos), forControlEvents: UIControlEvents.TouchUpInside) + self.btnCamera.setTitle(sendString, for: UIControlState()) + self.btnCamera.addTarget(self, action:#selector(AAConvActionSheet.sendPhotos), for: UIControlEvents.touchUpInside) self.btnCamera.titleLabel?.font = UIFont(name: "HelveticaNeue-Medium", size: 17) @@ -242,14 +242,14 @@ public class AAConvActionSheet: UIView, AAThumbnailViewDelegate { // // remove target // - self.btnCamera.removeTarget(self, action: #selector(AAConvActionSheet.sendPhotos), forControlEvents: UIControlEvents.TouchUpInside) + self.btnCamera.removeTarget(self, action: #selector(AAConvActionSheet.sendPhotos), for: UIControlEvents.touchUpInside) // // add new target // - self.btnCamera.setTitle(AALocalized("PhotoCamera"), forState: UIControlState.Normal) - self.btnCamera.addTarget(self, action: #selector(AAConvActionSheet.btnCameraAction), forControlEvents: UIControlEvents.TouchUpInside) - self.btnCamera.titleLabel?.font = UIFont.systemFontOfSize(17) + self.btnCamera.setTitle(AALocalized("PhotoCamera"), for: UIControlState()) + self.btnCamera.addTarget(self, action: #selector(AAConvActionSheet.btnCameraAction), for: UIControlEvents.touchUpInside) + self.btnCamera.titleLabel?.font = UIFont.systemFont(ofSize: 17) } } @@ -260,8 +260,8 @@ public class AAConvActionSheet: UIView, AAThumbnailViewDelegate { func sendPhotos() { if self.thumbnailView != nil { - self.thumbnailView.getSelectedAsImages { (images:[(NSData,Bool)]) -> () in - self.delegate?.actionSheetPickedImages(images) + self.thumbnailView.getSelectedAsImages { (images:[(Data,Bool)]) -> () in + (self.delegate?.actionSheetPickedImages(images))! } } dismiss() @@ -277,7 +277,7 @@ public class AAConvActionSheet: UIView, AAThumbnailViewDelegate { dismiss() } - func btnCustomAction(sender: UIButton) { + func btnCustomAction(_ sender: UIButton) { delegate?.actionSheetCustomButton(sender.tag) dismiss() } @@ -286,7 +286,7 @@ public class AAConvActionSheet: UIView, AAThumbnailViewDelegate { dismiss() } - public override func touchesBegan(touches: Set, withEvent event: UIEvent?) { + open override func touchesBegan(_ touches: Set, with event: UIEvent?) { dismiss() } } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Views/AARecordAudioController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Views/AARecordAudioController.swift index 874813a674..f048235081 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Views/AARecordAudioController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Views/AARecordAudioController.swift @@ -23,15 +23,15 @@ class AARecordAudioController: UIViewController,UIViewControllerTransitioningDel // var filePath : String! - var fileDuration : NSTimeInterval! + var fileDuration : TimeInterval! var recorded : Bool! = false - private let audioRecorder: AAAudioRecorder! = AAAudioRecorder() - private var audioPlayer: AAModernConversationAudioPlayer! + fileprivate let audioRecorder: AAAudioRecorder! = AAAudioRecorder() + fileprivate var audioPlayer: AAModernConversationAudioPlayer! - var meterTimer:NSTimer! - var soundFileURL:NSURL? + var meterTimer:Timer! + var soundFileURL:URL? //////////////////////////////// @@ -42,7 +42,7 @@ class AARecordAudioController: UIViewController,UIViewControllerTransitioningDel } - override init(nibName nibNameOrNil: String!, bundle nibBundleOrNil: NSBundle!) { + override init(nibName nibNameOrNil: String!, bundle nibBundleOrNil: Bundle!) { super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) self.commonInit() @@ -50,27 +50,27 @@ class AARecordAudioController: UIViewController,UIViewControllerTransitioningDel } func commonInit() { - self.modalPresentationStyle = .Custom + self.modalPresentationStyle = .custom self.transitioningDelegate = self } - override func preferredStatusBarStyle() -> UIStatusBarStyle { - return UIStatusBarStyle.LightContent + override var preferredStatusBarStyle : UIStatusBarStyle { + return UIStatusBarStyle.lightContent } // ---- UIViewControllerTransitioningDelegate methods - func presentationControllerForPresentedViewController(presented: UIViewController, presentingViewController presenting: UIViewController, sourceViewController source: UIViewController) -> UIPresentationController? { + func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? { if presented == self { - return AACustomPresentationController(presentedViewController: presented, presentingViewController: presenting) + return AACustomPresentationController(presentedViewController: presented, presenting: presenting) } return nil } - func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? { + func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { if presented == self { return AACustomPresentationAnimationController(isPresenting: true) @@ -80,7 +80,7 @@ class AARecordAudioController: UIViewController,UIViewControllerTransitioningDel } } - func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { + func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { if dismissed == self { return AACustomPresentationAnimationController(isPresenting: false) @@ -94,77 +94,77 @@ class AARecordAudioController: UIViewController,UIViewControllerTransitioningDel super.loadView() self.recorderView = UIView() - self.recorderView.frame = CGRectMake(self.view.frame.width/2 - 120, self.view.frame.height/2 - 80, 240, 160) - self.recorderView.backgroundColor = UIColor.whiteColor() + self.recorderView.frame = CGRect(x: self.view.frame.width/2 - 120, y: self.view.frame.height/2 - 80, width: 240, height: 160) + self.recorderView.backgroundColor = UIColor.white self.recorderView.layer.cornerRadius = 10 self.recorderView.layer.masksToBounds = true self.view.addSubview(self.recorderView) - self.buttonClose = UIButton(type: UIButtonType.System) - self.buttonClose.addTarget(self, action: #selector(AARecordAudioController.closeController), forControlEvents: UIControlEvents.TouchUpInside) - self.buttonClose.tintColor = UIColor.whiteColor() - self.buttonClose.setImage(UIImage.bundled("aa_closerecordbutton"), forState: UIControlState.Normal) - self.buttonClose.frame = CGRectMake(205, 5, 25, 25) + self.buttonClose = UIButton(type: UIButtonType.system) + self.buttonClose.addTarget(self, action: #selector(AARecordAudioController.closeController), for: UIControlEvents.touchUpInside) + self.buttonClose.tintColor = UIColor.white + self.buttonClose.setImage(UIImage.bundled("aa_closerecordbutton"), for: UIControlState()) + self.buttonClose.frame = CGRect(x: 205, y: 5, width: 25, height: 25) self.recorderView.addSubview(self.buttonClose) let separatorView = UIView() - separatorView.frame = CGRectMake(0, 80, 240, 0.5) - separatorView.backgroundColor = UIColor.grayColor() + separatorView.frame = CGRect(x: 0, y: 80, width: 240, height: 0.5) + separatorView.backgroundColor = UIColor.gray self.recorderView.addSubview(separatorView) self.timerLabel = UILabel() self.timerLabel.text = "00:00" self.timerLabel.font = UIFont(name: "HelveticaNeue-Medium", size: 17)! self.timerLabel.textColor = ActorSDK.sharedActor().style.vcHintColor - self.timerLabel.frame = CGRectMake(70, 5, 100, 40) - self.timerLabel.textAlignment = .Center - self.timerLabel.backgroundColor = UIColor.clearColor() + self.timerLabel.frame = CGRect(x: 70, y: 5, width: 100, height: 40) + self.timerLabel.textAlignment = .center + self.timerLabel.backgroundColor = UIColor.clear self.recorderView.addSubview(self.timerLabel) - self.startRecButton = UIButton(type: UIButtonType.System) - self.startRecButton.tintColor = UIColor.redColor() - self.startRecButton.setImage(UIImage.bundled("aa_startrecordbutton"), forState: UIControlState.Normal) - self.startRecButton.addTarget(self, action: #selector(AARecordAudioController.startRec), forControlEvents: UIControlEvents.TouchUpInside) - self.startRecButton.frame = CGRectMake(100, 110, 40, 40) + self.startRecButton = UIButton(type: UIButtonType.system) + self.startRecButton.tintColor = UIColor.red + self.startRecButton.setImage(UIImage.bundled("aa_startrecordbutton"), for: UIControlState()) + self.startRecButton.addTarget(self, action: #selector(AARecordAudioController.startRec), for: UIControlEvents.touchUpInside) + self.startRecButton.frame = CGRect(x: 100, y: 110, width: 40, height: 40) self.recorderView.addSubview(self.startRecButton) - self.stopRecButton = UIButton(type: UIButtonType.System) - self.stopRecButton.tintColor = UIColor.redColor() - self.stopRecButton.setImage(UIImage.bundled("aa_pauserecordbutton"), forState: UIControlState.Normal) - self.stopRecButton.addTarget(self, action: #selector(AARecordAudioController.stopRec), forControlEvents: UIControlEvents.TouchUpInside) - self.stopRecButton.frame = CGRectMake(100, 110, 40, 40) + self.stopRecButton = UIButton(type: UIButtonType.system) + self.stopRecButton.tintColor = UIColor.red + self.stopRecButton.setImage(UIImage.bundled("aa_pauserecordbutton"), for: UIControlState()) + self.stopRecButton.addTarget(self, action: #selector(AARecordAudioController.stopRec), for: UIControlEvents.touchUpInside) + self.stopRecButton.frame = CGRect(x: 100, y: 110, width: 40, height: 40) self.recorderView.addSubview(self.stopRecButton) - self.stopRecButton.hidden = true + self.stopRecButton.isHidden = true - self.playRecButton = UIButton(type: UIButtonType.System) - self.playRecButton.tintColor = UIColor.greenColor() - self.playRecButton.setImage(UIImage.bundled("aa_playrecordbutton"), forState: UIControlState.Normal) - self.playRecButton.addTarget(self, action: #selector(AARecordAudioController.play), forControlEvents: UIControlEvents.TouchUpInside) - self.playRecButton.frame = CGRectMake(100, 110, 40, 40) + self.playRecButton = UIButton(type: UIButtonType.system) + self.playRecButton.tintColor = UIColor.green + self.playRecButton.setImage(UIImage.bundled("aa_playrecordbutton"), for: UIControlState()) + self.playRecButton.addTarget(self, action: #selector(AARecordAudioController.play), for: UIControlEvents.touchUpInside) + self.playRecButton.frame = CGRect(x: 100, y: 110, width: 40, height: 40) self.recorderView.addSubview(self.playRecButton) - self.playRecButton.hidden = true + self.playRecButton.isHidden = true - self.sendRecord = UIButton(type: UIButtonType.System) - self.sendRecord.tintColor = UIColor.greenColor() - self.sendRecord.setImage(UIImage.bundled("aa_sendrecord"), forState: UIControlState.Normal) - self.sendRecord.addTarget(self, action: #selector(AARecordAudioController.sendRecordMessage), forControlEvents: UIControlEvents.TouchUpInside) - self.sendRecord.frame = CGRectMake(190, 115, 40, 40) - self.sendRecord.enabled = false + self.sendRecord = UIButton(type: UIButtonType.system) + self.sendRecord.tintColor = UIColor.green + self.sendRecord.setImage(UIImage.bundled("aa_sendrecord"), for: UIControlState()) + self.sendRecord.addTarget(self, action: #selector(AARecordAudioController.sendRecordMessage), for: UIControlEvents.touchUpInside) + self.sendRecord.frame = CGRect(x: 190, y: 115, width: 40, height: 40) + self.sendRecord.isEnabled = false self.recorderView.addSubview(self.sendRecord) - self.cleanRecord = UIButton(type: UIButtonType.System) - self.cleanRecord.tintColor = UIColor.redColor() - self.cleanRecord.setImage(UIImage.bundled("aa_deleterecord"), forState: UIControlState.Normal) - self.cleanRecord.addTarget(self, action: #selector(AARecordAudioController.sendRecordMessage), forControlEvents: UIControlEvents.TouchUpInside) - self.cleanRecord.frame = CGRectMake(10, 120, 30, 30) - self.cleanRecord.enabled = false + self.cleanRecord = UIButton(type: UIButtonType.system) + self.cleanRecord.tintColor = UIColor.red + self.cleanRecord.setImage(UIImage.bundled("aa_deleterecord"), for: UIControlState()) + self.cleanRecord.addTarget(self, action: #selector(AARecordAudioController.sendRecordMessage), for: UIControlEvents.touchUpInside) + self.cleanRecord.frame = CGRect(x: 10, y: 120, width: 30, height: 30) + self.cleanRecord.isEnabled = false self.recorderView.addSubview(self.cleanRecord) @@ -184,7 +184,7 @@ class AARecordAudioController: UIViewController,UIViewControllerTransitioningDel func closeController() { self.audioRecorder.cancel() - self.dismissViewControllerAnimated(true, completion: nil) + self.dismiss(animated: true, completion: nil) } @@ -193,9 +193,9 @@ class AARecordAudioController: UIViewController,UIViewControllerTransitioningDel //log.debug("recording. recorder nil") - playRecButton.hidden = true - stopRecButton.hidden = false - startRecButton.hidden = true + playRecButton.isHidden = true + stopRecButton.isHidden = false + startRecButton.isHidden = true startTimer() @@ -209,22 +209,22 @@ class AARecordAudioController: UIViewController,UIViewControllerTransitioningDel meterTimer.invalidate() - playRecButton.hidden = false - startRecButton.hidden = true - stopRecButton.hidden = true + playRecButton.isHidden = false + startRecButton.isHidden = true + stopRecButton.isHidden = true - audioRecorder.finish({ (path: String!, duration: NSTimeInterval) -> Void in + audioRecorder.finish({ (path: String?, duration: TimeInterval) -> Void in if (nil == path) { print("onAudioRecordingFinished: empty path") return } - self.filePath = path + self.filePath = path! self.fileDuration = duration - dispatch_async(dispatch_get_main_queue(), { () -> Void in - self.sendRecord.enabled = true - self.cleanRecord.enabled = true + DispatchQueue.main.async(execute: { () -> Void in + self.sendRecord.isEnabled = true + self.cleanRecord.isEnabled = true }) }) @@ -245,9 +245,9 @@ class AARecordAudioController: UIViewController,UIViewControllerTransitioningDel self.audioPlayer = AAModernConversationAudioPlayer(filePath:self.filePath) self.audioPlayer.play(0) - playRecButton.hidden = true - startRecButton.hidden = true - stopRecButton.hidden = false + playRecButton.isHidden = true + startRecButton.isHidden = true + stopRecButton.isHidden = false } @@ -255,12 +255,12 @@ class AARecordAudioController: UIViewController,UIViewControllerTransitioningDel func recordWithPermission() { let session:AVAudioSession = AVAudioSession.sharedInstance() // ios 8 and later - if (session.respondsToSelector(#selector(AVAudioSession.requestRecordPermission(_:)))) { + if (session.responds(to: #selector(AVAudioSession.requestRecordPermission(_:)))) { AVAudioSession.sharedInstance().requestRecordPermission({(granted: Bool)-> Void in if granted { print("Permission to record granted") self.setSessionPlayAndRecord() - self.meterTimer = NSTimer.scheduledTimerWithTimeInterval(0.1, + self.meterTimer = Timer.scheduledTimer(timeInterval: 0.1, target:self, selector:#selector(AARecordAudioController.updateAudioMeter(_:)), userInfo:nil, @@ -280,21 +280,21 @@ class AARecordAudioController: UIViewController,UIViewControllerTransitioningDel } func startTimer() { - self.meterTimer = NSTimer.scheduledTimerWithTimeInterval(0.1, + self.meterTimer = Timer.scheduledTimer(timeInterval: 0.1, target:self, selector:#selector(AARecordAudioController.updateAudioMeter(_:)), userInfo:nil, repeats:true) } - func updateAudioMeter(timer:NSTimer) { + func updateAudioMeter(_ timer:Timer) { if (self.audioRecorder != nil) { let dur = self.audioRecorder.currentDuration() let min = Int(dur / 60) - let sec = Int(dur % 60) + let sec = Int(dur.truncatingRemainder(dividingBy: 60)) let s = String(format: "%02d:%02d", min, sec) self.timerLabel.text = s @@ -336,7 +336,7 @@ class AARecordAudioController: UIViewController,UIViewControllerTransitioningDel func sendRecordMessage() { - self.dismissViewControllerAnimated(true, completion: nil) + self.dismiss(animated: true, completion: nil) //self.chatController.sendVoiceMessage(self.filePath, duration: self.fileDuration) } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Views/AAStickersKeyboard.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Views/AAStickersKeyboard.swift index 3d7da9fbad..c6d2b25e70 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Views/AAStickersKeyboard.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Views/AAStickersKeyboard.swift @@ -6,16 +6,16 @@ import UIKit import YYImage public protocol AAStickersKeyboardDelegate { - func stickerDidSelected(keyboard: AAStickersKeyboard, sticker: ACSticker) + func stickerDidSelected(_ keyboard: AAStickersKeyboard, sticker: ACSticker) } -public class AAStickersKeyboard: UIView, UICollectionViewDelegate, UICollectionViewDataSource { +open class AAStickersKeyboard: UIView, UICollectionViewDelegate, UICollectionViewDataSource { - public var delegate : AAStickersKeyboardDelegate? + open var delegate : AAStickersKeyboardDelegate? - private let collectionView: UICollectionView! - private var stickers = Array() - private let binder = AABinder() + fileprivate let collectionView: UICollectionView! + fileprivate var stickers = Array() + fileprivate let binder = AABinder() public override init(frame: CGRect) { @@ -24,8 +24,8 @@ public class AAStickersKeyboard: UIView, UICollectionViewDelegate, UICollectionV // layout for collection view let layoutCV = UICollectionViewFlowLayout() - layoutCV.scrollDirection = .Vertical - layoutCV.itemSize = CGSizeMake(widthHightItem, widthHightItem) + layoutCV.scrollDirection = .vertical + layoutCV.itemSize = CGSize(width: widthHightItem, height: widthHightItem) layoutCV.sectionInset = UIEdgeInsets(top: 5, left: 0, bottom: 10, right: 0) // init collection view @@ -41,27 +41,27 @@ public class AAStickersKeyboard: UIView, UICollectionViewDelegate, UICollectionV self.collectionView.dataSource = self self.collectionView.backgroundColor = UIColor(red: 0.7728, green: 0.8874, blue: 0.9365, alpha: 1.0) - self.collectionView.registerClass(AAStickersViewCell.self, forCellWithReuseIdentifier: "AAStickersViewCell") + self.collectionView.register(AAStickersViewCell.self, forCellWithReuseIdentifier: "AAStickersViewCell") self.collectionView.contentInset = UIEdgeInsetsMake(10, 5, 10, 5) self.collectionView.preservesSuperviewLayoutMargins = false - self.collectionView.layoutMargins = UIEdgeInsetsZero + self.collectionView.layoutMargins = UIEdgeInsets.zero // add collection view as subview self.addSubview(self.collectionView) - self.backgroundColor = UIColor.clearColor() + self.backgroundColor = UIColor.clear // Bind To Stickers - binder.bind(Actor.getAvailableStickersVM().getOwnStickerPacks()) { (value: JavaUtilArrayList!) -> () in - self.stickers.removeAll(keepCapacity: true) + binder.bind(Actor.getAvailableStickersVM().getOwnStickerPacks()) { (value: JavaUtilArrayList?) -> () in + self.stickers.removeAll(keepingCapacity: true) - for i in 0.. Int { + open func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return stickers.count } - public func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell { + open func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let stickerCell = collectionView - .dequeueReusableCellWithReuseIdentifier("AAStickersViewCell", forIndexPath: indexPath) as! AAStickersViewCell - stickerCell.bind(stickers[indexPath.row], clearPrev: true) + .dequeueReusableCell(withReuseIdentifier: "AAStickersViewCell", for: indexPath) as! AAStickersViewCell + stickerCell.bind(stickers[(indexPath as NSIndexPath).row], clearPrev: true) return stickerCell } - public func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) { - let sticker = stickers[indexPath.row] + open func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + let sticker = stickers[(indexPath as NSIndexPath).row] self.delegate?.stickerDidSelected(self, sticker: sticker) } } -public class AAStickersViewCell : UICollectionViewCell { +open class AAStickersViewCell : UICollectionViewCell { - private let stickerImage = AAStickerView() + fileprivate let stickerImage = AAStickerView() public override init(frame: CGRect) { super.init(frame: frame) - self.backgroundColor = UIColor.clearColor() - self.contentView.backgroundColor = UIColor.clearColor() + self.backgroundColor = UIColor.clear + self.contentView.backgroundColor = UIColor.clear self.addSubview(self.stickerImage) } @@ -120,7 +120,7 @@ public class AAStickersViewCell : UICollectionViewCell { fatalError("init(coder:) has not been implemented") } - public func bind(sticker: ACSticker!, clearPrev: Bool) { + open func bind(_ sticker: ACSticker!, clearPrev: Bool) { var fileLocation: ACFileReference? = sticker.getImage128() if sticker.getImage256() != nil { fileLocation = sticker.getImage256() @@ -128,12 +128,12 @@ public class AAStickersViewCell : UICollectionViewCell { self.stickerImage.setSticker(fileLocation) } - public override func layoutSubviews() { + open override func layoutSubviews() { super.layoutSubviews() self.stickerImage.frame = self.contentView.frame } - public override func prepareForReuse() { + open override func prepareForReuse() { super.prepareForReuse() self.stickerImage.setSticker(nil) } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Views/AAThumbnailCollectionCell.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Views/AAThumbnailCollectionCell.swift index 0f42b8efc1..c280582f0a 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Views/AAThumbnailCollectionCell.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Views/AAThumbnailCollectionCell.swift @@ -10,7 +10,7 @@ class AAThumbnailCollectionCell: UICollectionViewCell { let imgSelected : UIImageView! var animated : Bool! - var indexPath : NSIndexPath! + var indexPath : IndexPath! var isCheckSelected : Bool! weak var bindedThumbView : AAThumbnailView! weak var bindedPhotoModel : PHAsset! @@ -18,12 +18,12 @@ class AAThumbnailCollectionCell: UICollectionViewCell { override init(frame: CGRect) { self.imgThumbnails = UIImageView() - self.imgThumbnails.backgroundColor = UIColor.clearColor() + self.imgThumbnails.backgroundColor = UIColor.clear self.imgSelected = UIImageView() - self.imgSelected.backgroundColor = UIColor.clearColor() - self.imgSelected.userInteractionEnabled = true - self.imgSelected.contentMode = UIViewContentMode.ScaleAspectFill + self.imgSelected.backgroundColor = UIColor.clear + self.imgSelected.isUserInteractionEnabled = true + self.imgSelected.contentMode = UIViewContentMode.scaleAspectFill self.isCheckSelected = false self.animated = false @@ -49,10 +49,10 @@ class AAThumbnailCollectionCell: UICollectionViewCell { self.imgSelected.image = UIImage.bundled("ImageSelectedOn") - self.imgSelected.transform = CGAffineTransformMakeScale(0.5,0.5) - UIView.animateWithDuration(0.3, delay: 0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.5, options: UIViewAnimationOptions.CurveEaseInOut, animations: { () -> Void in + self.imgSelected.transform = CGAffineTransform(scaleX: 0.5,y: 0.5) + UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.5, options: UIViewAnimationOptions(), animations: { () -> Void in - self.imgSelected.transform = CGAffineTransformIdentity + self.imgSelected.transform = CGAffineTransform.identity }, completion: nil) @@ -64,10 +64,10 @@ class AAThumbnailCollectionCell: UICollectionViewCell { self.imgSelected.image = UIImage.bundled("ImageSelectedOff") - self.imgSelected.transform = CGAffineTransformMakeScale(0.5,0.5) - UIView.animateWithDuration(0.3, delay: 0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.5, options: UIViewAnimationOptions.CurveEaseInOut, animations: { () -> Void in + self.imgSelected.transform = CGAffineTransform(scaleX: 0.5,y: 0.5) + UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.5, options: UIViewAnimationOptions(), animations: { () -> Void in - self.imgSelected.transform = CGAffineTransformIdentity + self.imgSelected.transform = CGAffineTransform.identity }, completion: nil) @@ -89,9 +89,9 @@ class AAThumbnailCollectionCell: UICollectionViewCell { override func layoutSubviews() { super.layoutSubviews() - self.imgThumbnails.frame = CGRectMake(0, 0, 90, 90) + self.imgThumbnails.frame = CGRect(x: 0, y: 0, width: 90, height: 90) - self.imgSelected.frame = CGRectMake(self.contentView.frame.size.width-30, 4, 26, 26) + self.imgSelected.frame = CGRect(x: self.contentView.frame.size.width-30, y: 4, width: 26, height: 26) diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Views/AAThumbnailView.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Views/AAThumbnailView.swift index acfb072a7b..66c517e5ca 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Views/AAThumbnailView.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Views/AAThumbnailView.swift @@ -6,35 +6,35 @@ import UIKit import Photos public enum ImagePickerMediaType { - case Image - case Video - case ImageAndVideo + case image + case video + case imageAndVideo } public protocol AAThumbnailViewDelegate { - func thumbnailSelectedUpdated(selectedAssets: [(PHAsset,Bool)]) + func thumbnailSelectedUpdated(_ selectedAssets: [(PHAsset,Bool)]) } -public class AAThumbnailView: UIView,UICollectionViewDelegate , UICollectionViewDataSource { +open class AAThumbnailView: UIView,UICollectionViewDelegate , UICollectionViewDataSource { - public var delegate : AAThumbnailViewDelegate? + open var delegate : AAThumbnailViewDelegate? - private var collectionView:UICollectionView! - private let mediaType: ImagePickerMediaType = ImagePickerMediaType.Image + fileprivate var collectionView:UICollectionView! + fileprivate let mediaType: ImagePickerMediaType = ImagePickerMediaType.image - private var assets = [(PHAsset,Bool)]() - private var selectedAssets = [(PHAsset, Bool)]() - private var imageManager : PHCachingImageManager! + fileprivate var assets = [(PHAsset,Bool)]() + fileprivate var selectedAssets = [(PHAsset, Bool)]() + fileprivate var imageManager : PHCachingImageManager! - private let minimumPreviewHeight: CGFloat = 90 - private var maximumPreviewHeight: CGFloat = 90 + fileprivate let minimumPreviewHeight: CGFloat = 90 + fileprivate var maximumPreviewHeight: CGFloat = 90 - private let previewCollectionViewInset: CGFloat = 5 + fileprivate let previewCollectionViewInset: CGFloat = 5 - private lazy var requestOptions: PHImageRequestOptions = { + fileprivate lazy var requestOptions: PHImageRequestOptions = { let options = PHImageRequestOptions() - options.deliveryMode = .HighQualityFormat - options.resizeMode = .Fast + options.deliveryMode = .highQualityFormat + options.resizeMode = .fast return options }() @@ -52,22 +52,22 @@ public class AAThumbnailView: UIView,UICollectionViewDelegate , UICollectionView /// - public func open() { + open func open() { dispatchBackground { () -> Void in - if PHPhotoLibrary.authorizationStatus() == .Authorized { + if PHPhotoLibrary.authorizationStatus() == .authorized { self.imageManager = PHCachingImageManager() self.fetchAssets() - dispatch_async(dispatch_get_main_queue()) { + DispatchQueue.main.async { self.collectionView.reloadData() } - } else if PHPhotoLibrary.authorizationStatus() == .NotDetermined { + } else if PHPhotoLibrary.authorizationStatus() == .notDetermined { PHPhotoLibrary.requestAuthorization() { status in - if status == .Authorized { - dispatch_async(dispatch_get_main_queue()) { + if status == .authorized { + DispatchQueue.main.async { self.imageManager = PHCachingImageManager() self.fetchAssets() self.collectionView.reloadData() @@ -81,7 +81,7 @@ public class AAThumbnailView: UIView,UICollectionViewDelegate , UICollectionView } - private func fetchAssets() { + fileprivate func fetchAssets() { self.assets = [(PHAsset,Bool)]() let options = PHFetchOptions() @@ -96,71 +96,71 @@ public class AAThumbnailView: UIView,UICollectionViewDelegate , UICollectionView // options.predicate = NSPredicate(format: "mediaType = %d OR mediaType = %d", PHAssetMediaType.Image.rawValue, PHAssetMediaType.Video.rawValue) // } - options.predicate = NSPredicate(format: "mediaType = %d", PHAssetMediaType.Image.rawValue) + options.predicate = NSPredicate(format: "mediaType = %d", PHAssetMediaType.image.rawValue) let fetchLimit = 100 if #available(iOS 9, *) { options.fetchLimit = fetchLimit } - let result = PHAsset.fetchAssetsWithOptions(options) + let result = PHAsset.fetchAssets(with: options) let requestOptions = PHImageRequestOptions() - requestOptions.synchronous = true - requestOptions.deliveryMode = .FastFormat + requestOptions.isSynchronous = true + requestOptions.deliveryMode = .fastFormat - result.enumerateObjectsUsingBlock { asset, _, stop in - - if self.assets.count > fetchLimit { - stop.initialize(true) - } - - if let asset = asset as? PHAsset { - var isGIF = false - self.imageManager.requestImageDataForAsset(asset, options: requestOptions) { data, _, _, info in - if data != nil { - let gifMarker = info!["PHImageFileURLKey"] as! NSURL - print(gifMarker.pathExtension) - isGIF = (gifMarker.pathExtension == "GIF") ? true : false - print(isGIF) - self.prefetchImagesForAsset(asset) - } - self.assets.append((asset,isGIF)) - } - } - } +// result.enumerateObjects { asset, _, stop in +// +// if self.assets.count > fetchLimit { +// stop.initialize(to: true) +// } +// +// if let asset = asset as? PHAsset { +// var isGIF = false +// self.imageManager.requestImageData(for: asset, options: requestOptions) { data, _, _, info in +// if data != nil { +// let gifMarker = info!["PHImageFileURLKey"] as! URL +// print(gifMarker.pathExtension) +// isGIF = (gifMarker.pathExtension == "GIF") ? true : false +// print(isGIF) +// self.prefetchImagesForAsset(asset) +// } +// self.assets.append((asset,isGIF)) +// } +// } +// } } - private func prefetchImagesForAsset(asset: PHAsset) { - let targetSize = sizeForAsset(asset, scale: UIScreen.mainScreen().scale) - imageManager.startCachingImagesForAssets([asset], targetSize: targetSize, contentMode: .AspectFill, options: requestOptions) + fileprivate func prefetchImagesForAsset(_ asset: PHAsset) { + let targetSize = sizeForAsset(asset, scale: UIScreen.main.scale) + imageManager.startCachingImages(for: [asset], targetSize: targetSize, contentMode: .aspectFill, options: requestOptions) } - private func requestImageForAsset(asset: PHAsset, completion: (image: UIImage?) -> ()) { - let targetSize = sizeForAsset(asset, scale: UIScreen.mainScreen().scale) - requestOptions.synchronous = false + fileprivate func requestImageForAsset(_ asset: PHAsset, completion: @escaping (_ image: UIImage?) -> ()) { + let targetSize = sizeForAsset(asset, scale: UIScreen.main.scale) + requestOptions.isSynchronous = false // Workaround because PHImageManager.requestImageForAsset doesn't work for burst images if asset.representsBurst { - imageManager.requestImageDataForAsset(asset, options: requestOptions) { data, _, _, _ in + imageManager.requestImageData(for: asset, options: requestOptions) { data, _, _, _ in let image = data.flatMap { UIImage(data: $0) } - completion(image: image) + completion(image) } } else { - imageManager.requestImageForAsset(asset, targetSize: targetSize, contentMode: .AspectFill, options: requestOptions) { image, _ in - completion(image: image) + imageManager.requestImage(for: asset, targetSize: targetSize, contentMode: .aspectFill, options: requestOptions) { image, _ in + completion(image) } } } - private func sizeForAsset(asset: PHAsset, scale: CGFloat = 1) -> CGSize { + fileprivate func sizeForAsset(_ asset: PHAsset, scale: CGFloat = 1) -> CGSize { var complitedCGSize : CGSize! if asset.pixelWidth > asset.pixelHeight { - complitedCGSize = CGSizeMake(CGFloat(asset.pixelHeight),CGFloat(asset.pixelHeight)) + complitedCGSize = CGSize(width: CGFloat(asset.pixelHeight),height: CGFloat(asset.pixelHeight)) } else { - complitedCGSize = CGSizeMake(CGFloat(asset.pixelWidth),CGFloat(asset.pixelWidth)) + complitedCGSize = CGSize(width: CGFloat(asset.pixelWidth),height: CGFloat(asset.pixelWidth)) } return complitedCGSize @@ -168,22 +168,22 @@ public class AAThumbnailView: UIView,UICollectionViewDelegate , UICollectionView /// collection view delegate - public func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int { + open func numberOfSections(in collectionView: UICollectionView) -> Int { return 1 } - public func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + open func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return self.assets.count } - public func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell { + open func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { - let cell = self.collectionView.dequeueReusableCellWithReuseIdentifier("AAThumbnailCollectionCell", forIndexPath: indexPath) as! AAThumbnailCollectionCell + let cell = self.collectionView.dequeueReusableCell(withReuseIdentifier: "AAThumbnailCollectionCell", for: indexPath) as! AAThumbnailCollectionCell cell.bindedThumbView = self - let photoModel = self.assets[indexPath.row].0 - let animated = self.assets[indexPath.row].1 + let photoModel = self.assets[(indexPath as NSIndexPath).row].0 + let animated = self.assets[(indexPath as NSIndexPath).row].1 cell.bindedPhotoModel = photoModel @@ -196,18 +196,18 @@ public class AAThumbnailView: UIView,UICollectionViewDelegate , UICollectionView cell.imgSelected.image = UIImage.bundled("ImageSelectedOff") } - cell.backgroundColor = UIColor.whiteColor() + cell.backgroundColor = UIColor.white - let asset = assets[indexPath.row].0 + let asset = assets[(indexPath as NSIndexPath).row].0 requestImageForAsset(asset) { image in var complitedImage : UIImage! if image!.size.width > image!.size.height { - complitedImage = self.imageByCroppingImage(image!, toSize: CGSizeMake(image!.size.height,image!.size.height)) + complitedImage = self.imageByCroppingImage(image!, toSize: CGSize(width: image!.size.height,height: image!.size.height)) } else { - complitedImage = self.imageByCroppingImage(image!, toSize: CGSizeMake(image!.size.width,image!.size.width)) + complitedImage = self.imageByCroppingImage(image!, toSize: CGSize(width: image!.size.width,height: image!.size.width)) } cell.imgThumbnails.image = complitedImage @@ -221,79 +221,79 @@ public class AAThumbnailView: UIView,UICollectionViewDelegate , UICollectionView /// - public func reloadView() { + open func reloadView() { self.collectionView.reloadData() } - public func collectionViewSetup() { + open func collectionViewSetup() { let flowLayout = UICollectionViewFlowLayout() - flowLayout.scrollDirection = .Horizontal + flowLayout.scrollDirection = .horizontal flowLayout.minimumLineSpacing = 4 flowLayout.sectionInset = UIEdgeInsetsMake(5.0, 4.0, 5.0, 4.0) - flowLayout.itemSize = CGSizeMake(90, 90) + flowLayout.itemSize = CGSize(width: 90, height: 90) self.collectionView = UICollectionView(frame: self.bounds, collectionViewLayout: flowLayout) - self.collectionView.backgroundColor = UIColor.clearColor() + self.collectionView.backgroundColor = UIColor.clear self.collectionView.showsHorizontalScrollIndicator = false self.collectionView.delegate = self self.collectionView.dataSource = self - self.collectionView.registerClass(AAThumbnailCollectionCell.self, forCellWithReuseIdentifier: "AAThumbnailCollectionCell") + self.collectionView.register(AAThumbnailCollectionCell.self, forCellWithReuseIdentifier: "AAThumbnailCollectionCell") self.addSubview(self.collectionView) } - public func imageByCroppingImage(image:UIImage,toSize:CGSize) -> UIImage { + open func imageByCroppingImage(_ image:UIImage,toSize:CGSize) -> UIImage { - let refWidth = CGImageGetWidth(image.CGImage) - let refHeight = CGImageGetHeight(image.CGImage) + let refWidth = image.cgImage?.width + let refHeight = image.cgImage?.height - let x = CGFloat((refWidth - Int(toSize.width)) / 2) - let y = CGFloat((refHeight - Int(toSize.height)) / 2) + let x = CGFloat((refWidth! - Int(toSize.width)) / 2) + let y = CGFloat((refHeight! - Int(toSize.height)) / 2) - let cropRect = CGRectMake(x, y, toSize.height, toSize.width) - let imageRef = CGImageCreateWithImageInRect(image.CGImage, cropRect)! as CGImageRef + let cropRect = CGRect(x: x, y: y, width: toSize.height, height: toSize.width) + let imageRef = (image.cgImage?.cropping(to: cropRect)!)! as CGImage - let cropped = UIImage(CGImage: imageRef, scale: 0.0, orientation: UIImageOrientation.Up) + let cropped = UIImage(cgImage: imageRef, scale: 0.0, orientation: UIImageOrientation.up) return cropped } - public func addSelectedModel(model:PHAsset, animated:Bool) { + open func addSelectedModel(_ model:PHAsset, animated:Bool) { self.selectedAssets.append((model,animated)) self.delegate?.thumbnailSelectedUpdated(self.selectedAssets) } - public func removeSelectedModel(model:PHAsset,animated:Bool) { - for (index, element) in self.selectedAssets.enumerate() { + open func removeSelectedModel(_ model:PHAsset,animated:Bool) { + for (index, element) in self.selectedAssets.enumerated() { if element.0 == model { - self.selectedAssets.removeAtIndex(index) + self.selectedAssets.remove(at: index) } } self.delegate?.thumbnailSelectedUpdated(self.selectedAssets) } - public func getSelectedAsImages(completion: (images:[(NSData,Bool)]) -> ()) { + open func getSelectedAsImages(_ completion: @escaping (_ images:[(Data,Bool)]) -> ()) { let arrayModelsForSend = self.selectedAssets - var compliedArray = [(NSData,Bool)]() + var compliedArray = [(Data,Bool)]() var isGif = false - for (_,model) in arrayModelsForSend.enumerate() { - self.imageManager.requestImageDataForAsset(model.0, options: requestOptions, resultHandler: { (data, _, _, info) -> Void in + for (_,model) in arrayModelsForSend.enumerated() { + self.imageManager.requestImageData(for: model.0, options: requestOptions, resultHandler: { (data, _, _, info) -> Void in if data != nil { - let gifMarker = info!["PHImageFileURLKey"] as! NSURL + let gifMarker = info!["PHImageFileURLKey"] as! URL isGif = (gifMarker.pathExtension == "GIF") ? true : false print(isGif) compliedArray.append((data!,isGif)) if compliedArray.count == arrayModelsForSend.count { - completion(images: compliedArray) + completion(compliedArray) } } }) } } - public func dismiss() { + open func dismiss() { self.selectedAssets = [] self.reloadView() } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Views/AAVoiceRecorderView.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Views/AAVoiceRecorderView.swift index c0d1954eac..31b017a318 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Views/AAVoiceRecorderView.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Views/AAVoiceRecorderView.swift @@ -23,7 +23,7 @@ class AAVoiceRecorderView: UIView { ////////////////////////////////// weak var binedController : ConversationViewController! - var meterTimer:NSTimer! + var meterTimer:Timer! ////////////////////////////////// @@ -68,28 +68,28 @@ class AAVoiceRecorderView: UIView { self.backgroundColor = appStyle.chatInputFieldBgColor self.timeLabel.text = "0:00" - self.timeLabel.font = UIFont.systemFontOfSize(15) + self.timeLabel.font = UIFont.systemFont(ofSize: 15) self.timeLabel.textColor = appStyle.vcHintColor - self.timeLabel.frame = CGRectMake(29, 12, 50, 20) + self.timeLabel.frame = CGRect(x: 29, y: 12, width: 50, height: 20) self.sliderLabel.text = "Slide to cancel" - self.sliderLabel.font = UIFont.systemFontOfSize(14) - self.sliderLabel.textAlignment = .Left - self.sliderLabel.frame = CGRectMake(140,12,100,20) + self.sliderLabel.font = UIFont.systemFont(ofSize: 14) + self.sliderLabel.textAlignment = .left + self.sliderLabel.frame = CGRect(x: 140,y: 12,width: 100,height: 20) self.sliderLabel.textColor = appStyle.vcHintColor self.sliderArrow.image = UIImage.tinted("aa_recorderarrow", color: appStyle.vcHintColor) - self.sliderArrow.frame = CGRectMake(110,12,20,20) + self.sliderArrow.frame = CGRect(x: 110,y: 12,width: 20,height: 20) self.recorderImageCircle.image = UIImage.tinted("aa_recordercircle", color: UIColor(red: 0.7287, green: 0.7252, blue: 0.7322, alpha: 1.0)) - self.recorderImageCircle.frame = CGRectMake(10, 15, 14, 14) + self.recorderImageCircle.frame = CGRect(x: 10, y: 15, width: 14, height: 14) // } // update location views from track touch position - func updateLocation(offset:CGFloat,slideToRight:Bool) { + func updateLocation(_ offset:CGFloat,slideToRight:Bool) { var sliderLabelFrame = self.sliderLabel.frame sliderLabelFrame.origin.x += offset @@ -133,17 +133,17 @@ class AAVoiceRecorderView: UIView { func startAnimation() { - self.timeLabel.frame = CGRectMake(-129, 12, 50, 20) - self.sliderLabel.frame = CGRectMake(440,12,100,20) - self.sliderArrow.frame = CGRectMake(310,12,20,20) - self.recorderImageCircle.frame = CGRectMake(-110, 15, 14, 14) + self.timeLabel.frame = CGRect(x: -129, y: 12, width: 50, height: 20) + self.sliderLabel.frame = CGRect(x: 440,y: 12,width: 100,height: 20) + self.sliderArrow.frame = CGRect(x: 310,y: 12,width: 20,height: 20) + self.recorderImageCircle.frame = CGRect(x: -110, y: 15, width: 14, height: 14) - UIView.animateWithDuration(0.5, delay: 0.0, usingSpringWithDamping: 0.5, initialSpringVelocity: 1.0, options: UIViewAnimationOptions.CurveLinear, animations: { () -> Void in + UIView.animate(withDuration: 0.5, delay: 0.0, usingSpringWithDamping: 0.5, initialSpringVelocity: 1.0, options: UIViewAnimationOptions.curveLinear, animations: { () -> Void in - self.timeLabel.frame = CGRectMake(29, 12, 50, 20) - self.sliderLabel.frame = CGRectMake(140,12,100,20) - self.sliderArrow.frame = CGRectMake(110,12,20,20) - self.recorderImageCircle.frame = CGRectMake(10, 15, 14, 14) + self.timeLabel.frame = CGRect(x: 29, y: 12, width: 50, height: 20) + self.sliderLabel.frame = CGRect(x: 140,y: 12,width: 100,height: 20) + self.sliderArrow.frame = CGRect(x: 110,y: 12,width: 20,height: 20) + self.recorderImageCircle.frame = CGRect(x: 10, y: 15, width: 14, height: 14) }, completion: { (complite) -> Void in @@ -155,22 +155,22 @@ class AAVoiceRecorderView: UIView { func recordingStarted() { - UIView.animateWithDuration(0.3, animations: { () -> Void in + UIView.animate(withDuration: 0.3, animations: { () -> Void in self.recorderImageCircle.alpha = 0 - }) { (comp) -> Void in + }, completion: { (comp) -> Void in self.recorderImageCircle.image = UIImage.bundled("aa_recordercircle") - UIView.animateWithDuration(0.3, animations: { () -> Void in + UIView.animate(withDuration: 0.3, animations: { () -> Void in self.recorderImageCircle.alpha = 1 }) self.addAnimationsOnRecorderCircle() self.startUpdateTimer() - } + }) } @@ -180,29 +180,29 @@ class AAVoiceRecorderView: UIView { self.recorderImageCircle.layer.removeAllAnimations() self.recorderImageCircle.image = UIImage.tinted("aa_recordercircle", color: UIColor(red: 0.7287, green: 0.7252, blue: 0.7322, alpha: 1.0)) - self.timeLabel.frame = CGRectMake(29, 12, 50, 20) - self.sliderLabel.frame = CGRectMake(140,12,100,20) - self.sliderArrow.frame = CGRectMake(110,12,20,20) - self.recorderImageCircle.frame = CGRectMake(10, 15, 14, 14) + self.timeLabel.frame = CGRect(x: 29, y: 12, width: 50, height: 20) + self.sliderLabel.frame = CGRect(x: 140,y: 12,width: 100,height: 20) + self.sliderArrow.frame = CGRect(x: 110,y: 12,width: 20,height: 20) + self.recorderImageCircle.frame = CGRect(x: 10, y: 15, width: 14, height: 14) } func startUpdateTimer() { - self.meterTimer = NSTimer.scheduledTimerWithTimeInterval(0.1, + self.meterTimer = Timer.scheduledTimer(timeInterval: 0.1, target:self, selector:#selector(AAVoiceRecorderView.updateAudioMeter(_:)), userInfo:nil, repeats:true) } - func updateAudioMeter(timer:NSTimer) { + func updateAudioMeter(_ timer:Timer) { if let recorder = self.binedController?.audioRecorder { let dur = recorder.currentDuration() let minutes = Int(dur / 60) - let seconds = Int(dur % 60) + let seconds = Int(dur.truncatingRemainder(dividingBy: 60)) if seconds < 10 { self.timeLabel.text = "\(minutes):0\(seconds)" @@ -222,7 +222,7 @@ class AAVoiceRecorderView: UIView { circleAnimation.autoreverses = true circleAnimation.fromValue = 1.0 circleAnimation.toValue = 0.1 - self.recorderImageCircle.layer.addAnimation(circleAnimation, forKey: nil) + self.recorderImageCircle.layer.add(circleAnimation, forKey: nil) } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Dialogs List/AADialogsListContentController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Dialogs List/AADialogsListContentController.swift index 0b7b77c5fd..49927e5a09 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Dialogs List/AADialogsListContentController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Dialogs List/AADialogsListContentController.swift @@ -4,15 +4,15 @@ import Foundation -public class AADialogsListContentController: AAContentTableController, UISearchBarDelegate, UISearchDisplayDelegate { +open class AADialogsListContentController: AAContentTableController, UISearchBarDelegate, UISearchDisplayDelegate { - public var enableDeletion: Bool = true - public var enableSearch: Bool = true + open var enableDeletion: Bool = true + open var enableSearch: Bool = true - public var delegate: AADialogsListContentControllerDelegate! + open var delegate: AADialogsListContentControllerDelegate! public init() { - super.init(style: .Plain) + super.init(style: .plain) unbindOnDissapear = true } @@ -21,7 +21,7 @@ public class AADialogsListContentController: AAContentTableController, UISearchB fatalError("init(coder:) has not been implemented") } - public override func tableDidLoad() { + open override func tableDidLoad() { managedTable.canEditAll = true managedTable.canDeleteAll = true @@ -51,7 +51,7 @@ public class AADialogsListContentController: AAContentTableController, UISearchB r.animated = true r.displayList = Actor.getDialogsDisplayList() - if r.displayList.getListProcessor() == nil { + if r.displayList.getProcessor() == nil { r.displayList.setListProcessor(AADialogListProcessor()) } @@ -69,14 +69,14 @@ public class AADialogsListContentController: AAContentTableController, UISearchB r.editAction = { (dialog: ACDialog) -> () in if dialog.peer.isGroup { let g = Actor.getGroupWithGid(dialog.peer.peerId) - let isChannel = g.groupType == ACGroupType.CHANNEL() + let isChannel = g.groupType == ACGroupType.channel() self.alertSheet({ (a) in // Clear History if g.isCanClear.get().booleanValue() { a.action(AALocalized("ActionClearHistory"), closure: { self.confirmAlertUserDanger("ActionClearHistoryMessage", action: "ActionClearHistoryAction", tapYes: { - self.executeSafe(Actor.clearChatCommandWithPeer(dialog.peer)) + self.executeSafe(Actor.clearChatCommand(with: dialog.peer)) }) }) } @@ -86,26 +86,26 @@ public class AADialogsListContentController: AAContentTableController, UISearchB if isChannel { a.destructive(AALocalized("ActionLeaveChannel"), closure: { self.confirmAlertUserDanger("ActionLeaveChannelMessage", action: "ActionLeaveChannelAction", tapYes: { - self.executePromise(Actor.leaveAndDeleteGroupWithGid(dialog.peer.peerId)) + self.executePromise(Actor.leaveAndDeleteGroup(withGid: dialog.peer.peerId)) }) }) } else { a.destructive(AALocalized("ActionDeleteAndExit"), closure: { self.confirmAlertUserDanger("ActionDeleteAndExitMessage", action: "ActionDeleteAndExitAction", tapYes: { - self.executePromise(Actor.leaveAndDeleteGroupWithGid(dialog.peer.peerId)) + self.executePromise(Actor.leaveAndDeleteGroup(withGid: dialog.peer.peerId)) }) }) } } else if g.isCanDelete.get().booleanValue() && g.isMember.get().booleanValue(){ a.destructive(AALocalized(isChannel ? "ActionDeleteChannel" : "ActionDeleteGroup"), closure: { self.confirmAlertUserDanger(isChannel ? "ActionDeleteChannelMessage" : "ActionDeleteGroupMessage", action: "ActionDelete", tapYes: { - self.executePromise(Actor.deleteGroupWithGid(g.groupId)) + self.executePromise(Actor.deleteGroup(withGid: g.groupId)) }) }) } else { a.destructive(AALocalized("ActionDelete"), closure: { self.confirmAlertUserDanger("ActionDeleteMessage", action: "ActionDelete", tapYes: { - self.executeSafe(Actor.deleteChatCommandWithPeer(dialog.peer)) + self.executeSafe(Actor.deleteChatCommand(with: dialog.peer)) }) }) } @@ -117,10 +117,10 @@ public class AADialogsListContentController: AAContentTableController, UISearchB } else { self.alertSheet({ (a) in a.action(AALocalized("ActionClearHistory"), closure: { - self.executeSafe(Actor.clearChatCommandWithPeer(dialog.peer)) + self.executeSafe(Actor.clearChatCommand(with: dialog.peer)) }) a.destructive(AALocalized("ActionDelete"), closure: { - self.executeSafe(Actor.deleteChatCommandWithPeer(dialog.peer)) + self.executeSafe(Actor.deleteChatCommand(with: dialog.peer)) }) a.cancel = AALocalized("ActionCancel") }) @@ -138,7 +138,7 @@ public class AADialogsListContentController: AAContentTableController, UISearchB } - public override func viewWillAppear(animated: Bool) { + open override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) // Binding empty dialogs placeholder @@ -150,7 +150,7 @@ public class AADialogsListContentController: AAContentTableController, UISearchB self.showPlaceholder() } else { self.hidePlaceholder() - self.navigationItem.leftBarButtonItem = self.editButtonItem() + self.navigationItem.leftBarButtonItem = self.editButtonItem } } }) diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Dialogs List/AADialogsListContentControllerDelegate.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Dialogs List/AADialogsListContentControllerDelegate.swift index 26f9434ea8..372c4887ee 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Dialogs List/AADialogsListContentControllerDelegate.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Dialogs List/AADialogsListContentControllerDelegate.swift @@ -6,7 +6,7 @@ import Foundation public protocol AADialogsListContentControllerDelegate { - func recentsDidTap(controller: AADialogsListContentController, dialog: ACDialog) -> Bool + func recentsDidTap(_ controller: AADialogsListContentController, dialog: ACDialog) -> Bool - func searchDidTap(controller: AADialogsListContentController, entity: ACSearchResult) -} \ No newline at end of file + func searchDidTap(_ controller: AADialogsListContentController, entity: ACSearchResult) +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Dialogs List/Cells/AADialogCell.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Dialogs List/Cells/AADialogCell.swift index f86667f9a8..997c414f50 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Dialogs List/Cells/AADialogCell.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Dialogs List/Cells/AADialogCell.swift @@ -4,7 +4,7 @@ import UIKit -public class AADialogCell: AATableViewCell, AABindedCell { +open class AADialogCell: AATableViewCell, AABindedCell { // Binding data type @@ -12,48 +12,46 @@ public class AADialogCell: AATableViewCell, AABindedCell { // Hight of cell - public static func bindedCellHeight(table: AAManagedTable, item: ACDialog) -> CGFloat { + open static func bindedCellHeight(_ table: AAManagedTable, item: ACDialog) -> CGFloat { return 76 } // Cached design - private static let counterBgImage = Imaging - .imageWithColor(ActorSDK.sharedActor().style.dialogCounterBgColor, size: CGSizeMake(18, 18)) + fileprivate static let counterBgImage = Imaging + .imageWithColor(ActorSDK.sharedActor().style.dialogCounterBgColor, size: CGSize(width: 18, height: 18)) .roundImage(18) - .resizableImageWithCapInsets(UIEdgeInsetsMake(9, 9, 9, 9)) - private lazy var dialogTextActiveColor = ActorSDK.sharedActor().style.dialogTextActiveColor - private lazy var dialogTextColor = ActorSDK.sharedActor().style.dialogTextColor - private lazy var dialogStatusSending = ActorSDK.sharedActor().style.dialogStatusSending - private lazy var dialogStatusRead = ActorSDK.sharedActor().style.dialogStatusRead - private lazy var dialogStatusReceived = ActorSDK.sharedActor().style.dialogStatusReceived - private lazy var dialogStatusSent = ActorSDK.sharedActor().style.dialogStatusSent - private lazy var dialogStatusError = ActorSDK.sharedActor().style.dialogStatusError - private lazy var dialogAvatarSize = ActorSDK.sharedActor().style.dialogAvatarSize - private lazy var chatIconClock = ActorSDK.sharedActor().style.chatIconClock - private lazy var chatIconCheck2 = ActorSDK.sharedActor().style.chatIconCheck2 - private lazy var chatIconCheck1 = ActorSDK.sharedActor().style.chatIconCheck1 - private lazy var chatIconError = ActorSDK.sharedActor().style.chatIconError + .resizableImage(withCapInsets: UIEdgeInsetsMake(9, 9, 9, 9)) + fileprivate lazy var dialogTextActiveColor = ActorSDK.sharedActor().style.dialogTextActiveColor + fileprivate lazy var dialogTextColor = ActorSDK.sharedActor().style.dialogTextColor + fileprivate lazy var dialogStatusSending = ActorSDK.sharedActor().style.dialogStatusSending + fileprivate lazy var dialogStatusRead = ActorSDK.sharedActor().style.dialogStatusRead + fileprivate lazy var dialogStatusReceived = ActorSDK.sharedActor().style.dialogStatusReceived + fileprivate lazy var dialogStatusSent = ActorSDK.sharedActor().style.dialogStatusSent + fileprivate lazy var dialogStatusError = ActorSDK.sharedActor().style.dialogStatusError + fileprivate lazy var dialogAvatarSize = ActorSDK.sharedActor().style.dialogAvatarSize + fileprivate lazy var chatIconClock = ActorSDK.sharedActor().style.chatIconClock + fileprivate lazy var chatIconCheck2 = ActorSDK.sharedActor().style.chatIconCheck2 + fileprivate lazy var chatIconCheck1 = ActorSDK.sharedActor().style.chatIconCheck1 + fileprivate lazy var chatIconError = ActorSDK.sharedActor().style.chatIconError // Views - private var cellRenderer: AABackgroundCellRenderer! + fileprivate var cellRenderer: AABackgroundCellRenderer! - public let avatarView = AAAvatarView() - public let titleView = YYLabel() - public let messageView = YYLabel() - - public let dateView = YYLabel() - public let statusView = UIImageView() - public let counterView = YYLabel() - public let counterViewBg = UIImageView() - - private var isEditing = false + open let avatarView = AAAvatarView() + open let titleView = YYLabel() + open let messageView = YYLabel() + open let dateView = YYLabel() + open let statusView = UIImageView() + open let counterView = YYLabel() + open let counterViewBg = UIImageView() + // Binding Data - private var bindedItem: ACDialog? + fileprivate var bindedItem: ACDialog? public override init(style: UITableViewCellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) @@ -82,7 +80,7 @@ public class AADialogCell: AATableViewCell, AABindedCell { counterViewBg.image = AADialogCell.counterBgImage - statusView.contentMode = .Center + statusView.contentMode = .center self.contentView.addSubview(avatarView) self.contentView.addSubview(titleView) @@ -97,7 +95,7 @@ public class AADialogCell: AATableViewCell, AABindedCell { fatalError("init(coder:) has not been implemented") } - public func bind(item: ACDialog, table: AAManagedTable, index: Int, totalCount: Int) { + open func bind(_ item: ACDialog, table: AAManagedTable, index: Int, totalCount: Int) { // // Checking dialog rebinding @@ -110,7 +108,7 @@ public class AADialogCell: AATableViewCell, AABindedCell { var isRebind: Bool = false if let b = bindedItem { - if b.peer.isEqual(item.peer).boolValue { + if b.peer.isEqual(item.peer) { isRebind = true } } @@ -155,20 +153,20 @@ public class AADialogCell: AATableViewCell, AABindedCell { // if item.senderId != Actor.myUid() { - self.statusView.hidden = true + self.statusView.isHidden = true } else { if item.isRead() { self.statusView.tintColor = dialogStatusRead self.statusView.image = chatIconCheck2 - self.statusView.hidden = false + self.statusView.isHidden = false } else if item.isReceived() { self.statusView.tintColor = dialogStatusReceived self.statusView.image = chatIconCheck2 - self.statusView.hidden = false + self.statusView.isHidden = false } else { self.statusView.tintColor = dialogStatusSent self.statusView.image = chatIconCheck1 - self.statusView.hidden = false + self.statusView.isHidden = false } } @@ -178,17 +176,17 @@ public class AADialogCell: AATableViewCell, AABindedCell { setNeedsLayout() } - public override func willTransitionToState(state: UITableViewCellStateMask) { - super.willTransitionToState(state) + open override func willTransition(to state: UITableViewCellStateMask) { + super.willTransition(to: state) - if state.contains(UITableViewCellStateMask.ShowingEditControlMask) { + if state.contains(UITableViewCellStateMask.showingEditControlMask) { isEditing = true } else { isEditing = false } } - public override func layoutSubviews() { + open override func layoutSubviews() { super.layoutSubviews() // We expect height == 76; @@ -200,13 +198,13 @@ public class AADialogCell: AATableViewCell, AABindedCell { // Avatar View // let avatarPadding = padding + (50 - dialogAvatarSize) / 2 - avatarView.frame = CGRectMake(avatarPadding, avatarPadding, dialogAvatarSize, dialogAvatarSize) + avatarView.frame = CGRect(x: avatarPadding, y: avatarPadding, width: dialogAvatarSize, height: dialogAvatarSize) // // Title // - let titleFrame = CGRectMake(leftPadding, 16, width - leftPadding - /*paddingRight*/(padding + 50), 21) + let titleFrame = CGRect(x: leftPadding, y: 16, width: width - leftPadding - /*paddingRight*/(padding + 50), height: 21) UIView.performWithoutAnimation { self.titleView.frame = titleFrame } @@ -216,8 +214,8 @@ public class AADialogCell: AATableViewCell, AABindedCell { // Status Icon // - if (!self.statusView.hidden) { - statusView.frame = CGRectMake(leftPadding, 44, 20, 18) + if (!self.statusView.isHidden) { + statusView.frame = CGRect(x: leftPadding, y: 44, width: 20, height: 18) } @@ -228,7 +226,7 @@ public class AADialogCell: AATableViewCell, AABindedCell { if bindedItem != nil { let config = AADialogCellConfig( item: bindedItem!, - isStatusVisible: !statusView.hidden, + isStatusVisible: !statusView.isHidden, titleWidth: titleFrame.width, contentWidth: width) @@ -247,7 +245,7 @@ public class AADialogCell: AATableViewCell, AABindedCell { } } - private func cellRender(config: AADialogCellConfig) -> AADialogCellLayout! { + fileprivate func cellRender(_ config: AADialogCellConfig) -> AADialogCellLayout! { // // Title Layouting @@ -258,7 +256,7 @@ public class AADialogCell: AATableViewCell, AABindedCell { title.yy_color = appStyle.dialogTitleColor let titleContainer = YYTextContainer(size: CGSize(width: config.titleWidth, height: 1000)) titleContainer.maximumNumberOfRows = 1 - titleContainer.truncationType = .End + titleContainer.truncationType = .end let titleLayout = YYTextLayout(container: titleContainer, text: title)! @@ -280,7 +278,7 @@ public class AADialogCell: AATableViewCell, AABindedCell { let counterLayout: YYTextLayout? if config.item.unreadCount > 0 { let counter = NSMutableAttributedString(string: "\(config.item.unreadCount)") - counter.yy_font = UIFont.systemFontOfSize(14) + counter.yy_font = UIFont.systemFont(ofSize: 14) counter.yy_color = appStyle.dialogCounterColor counterLayout = YYTextLayout(containerSize: CGSize(width: 1000, height: 1000), text: counter)! unreadPadding = max(counterLayout!.textBoundingSize.width + 8, 18) @@ -294,8 +292,8 @@ public class AADialogCell: AATableViewCell, AABindedCell { // let message = NSMutableAttributedString(string: Actor.getFormatter().formatDialogText(config.item)) - message.yy_font = UIFont.systemFontOfSize(16) - if config.item.messageType.ordinal() != ACContentType.TEXT().ordinal() { + message.yy_font = UIFont.systemFont(ofSize: 16) + if config.item.messageType.ordinal() != ACContentType.text().ordinal() { message.yy_color = dialogTextActiveColor } else { message.yy_color = dialogTextColor @@ -303,7 +301,7 @@ public class AADialogCell: AATableViewCell, AABindedCell { let messageWidth = config.contentWidth - 76 - 14 - messagePadding - unreadPadding let messageContainer = YYTextContainer(size: CGSize(width: messageWidth, height: 1000)) messageContainer.maximumNumberOfRows = 1 - messageContainer.truncationType = .End + messageContainer.truncationType = .end let messageLayout = YYTextLayout(container: messageContainer, text: message)! @@ -319,14 +317,14 @@ public class AADialogCell: AATableViewCell, AABindedCell { } let dateAtrStr = NSMutableAttributedString(string: dateStr) dateAtrStr.yy_color = appStyle.dialogDateColor - dateAtrStr.yy_font = UIFont.systemFontOfSize(14) + dateAtrStr.yy_font = UIFont.systemFont(ofSize: 14) let dateContainer = YYTextContainer(size: CGSize(width: 60, height: 1000)) let dateLayout = YYTextLayout(container: dateContainer, text: dateAtrStr)! return AADialogCellLayout(titleLayout: titleLayout, messageLayout: messageLayout, messageWidth: messageWidth, counterLayout: counterLayout, dateLayout: dateLayout) } - private func cellApply(render: AADialogCellLayout!) { + fileprivate func cellApply(_ render: AADialogCellLayout!) { // // Avatar @@ -356,7 +354,7 @@ public class AADialogCell: AATableViewCell, AABindedCell { dateView.textLayout = render.dateLayout let dateWidth = render.dateLayout.textBoundingSize.width - dateView.frame = CGRectMake(contentView.width - dateWidth - leftPadding, 18, dateWidth, 18) + dateView.frame = CGRect(x: contentView.width - dateWidth - leftPadding, y: 18, width: dateWidth, height: 18) presentView(dateView) @@ -365,10 +363,10 @@ public class AADialogCell: AATableViewCell, AABindedCell { // var padding: CGFloat = 76 - if !statusView.hidden { + if !statusView.isHidden { padding += 22 } - let messageViewFrame = CGRectMake(padding, 44, render.messageWidth, 18) + let messageViewFrame = CGRect(x: padding, y: 44, width: render.messageWidth, height: 18) UIView.performWithoutAnimation { self.messageView.frame = messageViewFrame } @@ -379,7 +377,7 @@ public class AADialogCell: AATableViewCell, AABindedCell { // // Message State // - if !self.statusView.hidden { + if !self.statusView.isHidden { presentView(self.statusView) } @@ -394,8 +392,8 @@ public class AADialogCell: AATableViewCell, AABindedCell { let textW = render.counterLayout!.textBoundingSize.width let unreadW = max(textW + 8, 18) - counterView.frame = CGRectMake(contentView.width - leftPadding - unreadW + (unreadW - textW) / 2, 44, textW, 18) - counterViewBg.frame = CGRectMake(contentView.width - leftPadding - unreadW, 44, unreadW, 18) + counterView.frame = CGRect(x: contentView.width - leftPadding - unreadW + (unreadW - textW) / 2, y: 44, width: textW, height: 18) + counterViewBg.frame = CGRect(x: contentView.width - leftPadding - unreadW, y: 44, width: unreadW, height: 18) presentView(counterView) presentView(counterViewBg) @@ -406,11 +404,11 @@ public class AADialogCell: AATableViewCell, AABindedCell { } } - private func presentView(view: UIView) { + fileprivate func presentView(_ view: UIView) { view.alpha = 1 } - private func dismissView(view: UIView) { + fileprivate func dismissView(_ view: UIView) { view.alpha = 0 } } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Dialogs List/Cells/AADialogListProcessor.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Dialogs List/Cells/AADialogListProcessor.swift index fefaef7a4f..f5b4c11c79 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Dialogs List/Cells/AADialogListProcessor.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Dialogs List/Cells/AADialogListProcessor.swift @@ -6,12 +6,12 @@ import Foundation class AADialogListProcessor: NSObject, ARListProcessor { - func processWithItems(items: JavaUtilList, withPrevious previous: AnyObject?) -> AnyObject? { + func process(withItems items: JavaUtilList, withPrevious previous: Any?) -> Any? { var uids = Set() for i in 0.. CGFloat { + open static func bindedCellHeight(_ item: BindData) -> CGFloat { return 76 } - private let avatarView: AAAvatarView = AAAvatarView() - private let titleView: UILabel = UILabel() + fileprivate let avatarView: AAAvatarView = AAAvatarView() + fileprivate let titleView: UILabel = UILabel() public override init(style: UITableViewCellStyle, reuseIdentifier: String?) { - super.init(style: UITableViewCellStyle.Default, reuseIdentifier: reuseIdentifier) + super.init(style: UITableViewCellStyle.default, reuseIdentifier: reuseIdentifier) titleView.font = UIFont.mediumSystemFontOfSize(19) titleView.textColor = ActorSDK.sharedActor().style.dialogTextColor @@ -31,24 +31,24 @@ public class AADialogSearchCell: AATableViewCell, AABindedSearchCell { fatalError("init(coder:) has not been implemented") } - public func bind(item: ACSearchResult, search: String?) { + open func bind(_ item: ACSearchResult, search: String?) { avatarView.bind(item.title, id: Int(item.peer.peerId), avatar: item.avatar) titleView.text = item.title } - public override func prepareForReuse() { + open override func prepareForReuse() { super.prepareForReuse() avatarView.unbind() } - public override func layoutSubviews() { + open override func layoutSubviews() { super.layoutSubviews() let width = self.contentView.frame.width let leftPadding = CGFloat(76) let padding = CGFloat(14) - avatarView.frame = CGRectMake(padding, padding, 52, 52) - titleView.frame = CGRectMake(leftPadding, 0, width - leftPadding - (padding + 50), contentView.bounds.size.height) + avatarView.frame = CGRect(x: padding, y: padding, width: 52, height: 52) + titleView.frame = CGRect(x: leftPadding, y: 0, width: width - leftPadding - (padding + 50), height: contentView.bounds.size.height) } } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Input/AAEditFieldController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Input/AAEditFieldController.swift index ddaeee44d4..14e5db5675 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Input/AAEditFieldController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Input/AAEditFieldController.swift @@ -4,20 +4,20 @@ import Foundation -public class AAEditFieldControllerConfig { +open class AAEditFieldControllerConfig { - public var title: String! - public var actionTitle: String! - public var hint: String! - public var initialText: String! + open var title: String! + open var actionTitle: String! + open var hint: String! + open var initialText: String! - public var fieldReturnKey: UIReturnKeyType = .Default - public var fieldHint: String! - public var fieldAutocorrectionType = UITextAutocorrectionType.Default - public var fieldAutocapitalizationType = UITextAutocapitalizationType.Sentences + open var fieldReturnKey: UIReturnKeyType = .default + open var fieldHint: String! + open var fieldAutocorrectionType = UITextAutocorrectionType.default + open var fieldAutocapitalizationType = UITextAutocapitalizationType.sentences - public var didDismissTap: ((c: AAEditFieldController)->())? - public var didDoneTap: ((t: String, c: AAEditFieldController)->())? + open var didDismissTap: ((_ c: AAEditFieldController)->())? + open var didDoneTap: ((_ t: String, _ c: AAEditFieldController)->())? func check() { if title == nil { @@ -26,25 +26,25 @@ public class AAEditFieldControllerConfig { } } -public class AAEditFieldController: AAContentTableController { +open class AAEditFieldController: AAContentTableController { - public var fieldCell: AAEditRow! + open var fieldCell: AAEditRow! - public let config: AAEditFieldControllerConfig + open let config: AAEditFieldControllerConfig public init(config: AAEditFieldControllerConfig) { self.config = config - super.init(style: .SettingsGrouped) + super.init(style: .settingsGrouped) navigationItem.title = AALocalized(config.title) - navigationItem.leftBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationCancel"), style: .Plain, target: self, action: #selector(AAEditFieldController.doDismiss)) + navigationItem.leftBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationCancel"), style: .plain, target: self, action: #selector(AAEditFieldController.doDismiss)) if config.actionTitle != nil { - navigationItem.rightBarButtonItem = UIBarButtonItem(title: AALocalized(config.actionTitle), style: .Done, target: self, action: #selector(AAEditFieldController.doAction)) + navigationItem.rightBarButtonItem = UIBarButtonItem(title: AALocalized(config.actionTitle), style: .done, target: self, action: #selector(AAEditFieldController.doAction)) } else { - navigationItem.rightBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationDone"), style: .Done, target: self, action: #selector(AAEditFieldController.doAction)) + navigationItem.rightBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationDone"), style: .done, target: self, action: #selector(AAEditFieldController.doAction)) } } @@ -52,7 +52,7 @@ public class AAEditFieldController: AAContentTableController { fatalError("init(coder:) has not been implemented") } - public override func tableDidLoad() { + open override func tableDidLoad() { section { (s) -> () in @@ -83,30 +83,30 @@ public class AAEditFieldController: AAContentTableController { } let text = fieldCell.text!.trim() - config.didDoneTap?(t: text, c: self) + config.didDoneTap?(text, self) } func doDismiss() { if config.didDismissTap != nil { - config.didDismissTap!(c: self) + config.didDismissTap!(self) } else { dismiss() } } - public override func viewWillAppear(animated: Bool) { + open override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - if let c = tableView.cellForRowAtIndexPath(NSIndexPath(forRow: 0, inSection: 0)) as? AAEditCell { + if let c = tableView.cellForRow(at: IndexPath(row: 0, section: 0)) as? AAEditCell { c.textField.becomeFirstResponder() } } - public override func viewWillDisappear(animated: Bool) { + open override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) - if let c = tableView.cellForRowAtIndexPath(NSIndexPath(forRow: 0, inSection: 0)) as? AAEditCell { + if let c = tableView.cellForRow(at: IndexPath(row: 0, section: 0)) as? AAEditCell { c.textField.resignFirstResponder() } } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Input/AAEditTextController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Input/AAEditTextController.swift index 78ab38ed0f..b10839a32b 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Input/AAEditTextController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Input/AAEditTextController.swift @@ -5,16 +5,16 @@ import Foundation import SZTextView -public class AAEditTextControllerConfig { +open class AAEditTextControllerConfig { - public var title: String! - public var hint: String! - public var actionTitle: String! - public var initialText: String! + open var title: String! + open var hint: String! + open var actionTitle: String! + open var initialText: String! - public var didDismissTap: ((AAEditTextController) -> ())! + open var didDismissTap: ((AAEditTextController) -> ())! - public var didCompleteTap: ((String, AAEditTextController) -> ())! + open var didCompleteTap: ((String, AAEditTextController) -> ())! func check() { @@ -27,11 +27,11 @@ public class AAEditTextControllerConfig { } } -public class AAEditTextController: AAViewController { +open class AAEditTextController: AAViewController { - private let config: AAEditTextControllerConfig + fileprivate let config: AAEditTextControllerConfig - private var textView = SZTextView() + fileprivate var textView = SZTextView() public init(config: AAEditTextControllerConfig) { @@ -42,49 +42,49 @@ public class AAEditTextController: AAViewController { self.navigationItem.title = AALocalized(config.title) if config.actionTitle != nil { - self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: AALocalized(config.actionTitle), style: UIBarButtonItemStyle.Done, target: self, action: #selector(AAEditTextController.doSave)) + self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: AALocalized(config.actionTitle), style: UIBarButtonItemStyle.done, target: self, action: #selector(AAEditTextController.doSave)) } else { - self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationDone"), style: UIBarButtonItemStyle.Done, target: self, action: #selector(AAEditTextController.doSave)) + self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationDone"), style: UIBarButtonItemStyle.done, target: self, action: #selector(AAEditTextController.doSave)) } - self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationCancel"), style: UIBarButtonItemStyle.Plain, target: self, action: #selector(AAEditTextController.doCancel)) + self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationCancel"), style: UIBarButtonItemStyle.plain, target: self, action: #selector(AAEditTextController.doCancel)) self.textView.fadeTime = 0 if let h = config.hint { self.textView.placeholder = AALocalized(h) } self.textView.text = config.initialText - self.textView.font = UIFont.systemFontOfSize(18) + self.textView.font = UIFont.systemFont(ofSize: 18) - self.view.backgroundColor = UIColor.whiteColor() + self.view.backgroundColor = UIColor.white } public required init(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - public override func viewDidLoad() { + open override func viewDidLoad() { super.viewDidLoad() self.view.addSubview(textView) } - public override func viewWillAppear(animated: Bool) { + open override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) self.textView.becomeFirstResponder() } - public override func viewWillDisappear(animated: Bool) { + open override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) self.textView.resignFirstResponder() } - public func doSave() { + open func doSave() { self.config.didCompleteTap?(textView.text, self) } - public func doCancel() { + open func doCancel() { if self.config.didDismissTap != nil { self.config.didDismissTap!(self) } else { @@ -92,10 +92,10 @@ public class AAEditTextController: AAViewController { } } - public override func viewDidLayoutSubviews() { + open override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() - self.textView.frame = CGRectMake(7, 7, self.view.bounds.width - 14, self.view.bounds.height - 14) + self.textView.frame = CGRect(x: 7, y: 7, width: self.view.bounds.width - 14, height: self.view.bounds.height - 14) } } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Previews/AACorePreviewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Previews/AACorePreviewController.swift index 4c1d54984e..f18ae48271 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Previews/AACorePreviewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Previews/AACorePreviewController.swift @@ -4,7 +4,7 @@ import Foundation -public class AACodePreviewController: AAViewController { +open class AACodePreviewController: AAViewController { var webView = UIWebView() let code: String @@ -18,7 +18,7 @@ public class AACodePreviewController: AAViewController { fatalError("init(coder:) has not been implemented") } - public override func viewDidLoad() { + open override func viewDidLoad() { super.viewDidLoad() navigationItem.title = "Source Code" @@ -34,17 +34,17 @@ public class AACodePreviewController: AAViewController { "\n" + "" - let bundle = NSBundle.framework - let path = bundle.pathForResource("highlight.min", ofType: "js")! + let bundle = Bundle.framework + let path = bundle.path(forResource: "highlight.min", ofType: "js")! - webView.loadHTMLString(data, baseURL: NSURL(fileURLWithPath: path)) + webView.loadHTMLString(data, baseURL: URL(fileURLWithPath: path)) view.addSubview(webView) } - public override func viewDidLayoutSubviews() { + open override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() webView.frame = view.bounds } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Previews/AAPhotoPreviewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Previews/AAPhotoPreviewController.swift index e7f14f8b0e..24439ef926 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Previews/AAPhotoPreviewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Previews/AAPhotoPreviewController.swift @@ -4,7 +4,7 @@ import Foundation -public class AAPhotoPreviewController: NYTPhotosViewController, NYTPhotosViewControllerDelegate { +open class AAPhotoPreviewController: NYTPhotosViewController, NYTPhotosViewControllerDelegate { var autoShowBadge = false @@ -35,7 +35,7 @@ public class AAPhotoPreviewController: NYTPhotosViewController, NYTPhotosViewCon } if p.file != nil { - let desc = Actor.findDownloadedDescriptorWithFileId(p.file!.getFileId()) + let desc = Actor.findDownloadedDescriptor(withFileId: p.file!.getFileId()) if desc != nil { let img = UIImage(contentsOfFile: CocoaFiles.pathFromDescriptor(desc!)) if img != nil { @@ -46,7 +46,7 @@ public class AAPhotoPreviewController: NYTPhotosViewController, NYTPhotosViewCon } if p.previewFile != nil { - let desc = Actor.findDownloadedDescriptorWithFileId(p.previewFile!.getFileId()) + let desc = Actor.findDownloadedDescriptor(withFileId: p.previewFile!.getFileId()) if desc != nil { var img = UIImage(contentsOfFile: CocoaFiles.pathFromDescriptor(desc!)) if img != nil { @@ -77,11 +77,11 @@ public class AAPhotoPreviewController: NYTPhotosViewController, NYTPhotosViewCon fatalError("init(coder:) has not been implemented") } - public override func viewWillAppear(animated: Bool) { + open override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) // Setting tint color - navigationController?.navigationBar.tintColor = UIColor.whiteColor() + navigationController?.navigationBar.tintColor = UIColor.white // Binding files for i in 0.. Void in cp.image = image - self.updateImageForPhoto(cp) + self.updateImage(for: cp) }) }) - Actor.bindRawFileWithReference(p.file!, autoStart: true, withCallback: callback) + Actor.bindRawFile(with: p.file!, autoStart: true, with: callback) bind[i] = callback } } // Hide Status bar - UIApplication.sharedApplication().animateStatusBarAppearance(.SlideUp, duration: 0.3) + UIApplication.shared.animateStatusBarAppearance(.slideUp, duration: 0.3) // Hide badge if autoShowBadge { @@ -115,21 +115,21 @@ public class AAPhotoPreviewController: NYTPhotosViewController, NYTPhotosViewCon } } - public func photosViewController(photosViewController: NYTPhotosViewController, referenceViewForPhoto photo: NYTPhoto) -> UIView? { + open func photosViewController(_ photosViewController: NYTPhotosViewController, referenceViewFor photo: NYTPhoto) -> UIView? { return self.fromView } - public override func viewWillDisappear(animated: Bool) { + open override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) // Unbind all for i in bind { - Actor.unbindRawFileWithFileId(photos[i.0].file!.getFileId(), autoCancel: true, withCallback: i.1) + Actor.unbindRawFile(withFileId: photos[i.0].file!.getFileId(), autoCancel: true, with: i.1) } bind.removeAll() // Restoring status bar - UIApplication.sharedApplication().animateStatusBarAppearance(.SlideDown, duration: 0.3) + UIApplication.shared.animateStatusBarAppearance(.slideDown, duration: 0.3) // Restoring badge if autoShowBadge { @@ -138,7 +138,7 @@ public class AAPhotoPreviewController: NYTPhotosViewController, NYTPhotosViewCon } } -public class PreviewImage { +open class PreviewImage { let preview: UIImage? var image: UIImage? @@ -166,7 +166,7 @@ public class PreviewImage { class AAPhoto: NSObject, NYTPhoto { var image: UIImage? - var imageData: NSData? + var imageData: Data? var placeholderImage: UIImage? let attributedCaptionTitle: NSAttributedString? let attributedCaptionSummary: NSAttributedString? @@ -224,4 +224,4 @@ class AAPhoto: NSObject, NYTPhoto { public var attributedCaptionCredit: NSAttributedString? { get } */ -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Previews/AAWallpapperPreviewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Previews/AAWallpapperPreviewController.swift index bd1e30d2b1..0b1ce59da1 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Previews/AAWallpapperPreviewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Previews/AAWallpapperPreviewController.swift @@ -4,15 +4,15 @@ import Foundation -public class AAWallpapperPreviewController: AAViewController { +open class AAWallpapperPreviewController: AAViewController { - private let imageView = UIImageView() - private let cancelButton = UIButton() - private let setButton = UIButton() + fileprivate let imageView = UIImageView() + fileprivate let cancelButton = UIButton() + fileprivate let setButton = UIButton() - private let imageName: String - private let selectedImage: UIImage - private var fromName: Bool + fileprivate let imageName: String + fileprivate let selectedImage: UIImage + fileprivate var fromName: Bool public init(imageName: String) { self.imageName = imageName @@ -20,16 +20,16 @@ public class AAWallpapperPreviewController: AAViewController { self.fromName = true super.init() imageView.image = UIImage.bundled(imageName)! - imageView.contentMode = .ScaleAspectFill + imageView.contentMode = .scaleAspectFill imageView.clipsToBounds = true cancelButton.backgroundColor = appStyle.vcPanelBgColor - cancelButton.addTarget(self, action: #selector(AAWallpapperPreviewController.cancelDidTap), forControlEvents: .TouchUpInside) - cancelButton.setTitle(AALocalized("AlertCancel"), forState: .Normal) - cancelButton.setTitleColor(appStyle.tabUnselectedTextColor, forState: .Normal) + cancelButton.addTarget(self, action: #selector(AAWallpapperPreviewController.cancelDidTap), for: .touchUpInside) + cancelButton.setTitle(AALocalized("AlertCancel"), for: UIControlState()) + cancelButton.setTitleColor(appStyle.tabUnselectedTextColor, for: UIControlState()) setButton.backgroundColor = appStyle.vcPanelBgColor - setButton.addTarget(self, action: #selector(AAWallpapperPreviewController.setDidTap), forControlEvents: .TouchUpInside) - setButton.setTitle(AALocalized("AlertSet"), forState: .Normal) - setButton.setTitleColor(appStyle.tabUnselectedTextColor, forState: .Normal) + setButton.addTarget(self, action: #selector(AAWallpapperPreviewController.setDidTap), for: .touchUpInside) + setButton.setTitle(AALocalized("AlertSet"), for: UIControlState()) + setButton.setTitleColor(appStyle.tabUnselectedTextColor, for: UIControlState()) } public init(selectedImage: UIImage) { @@ -38,33 +38,33 @@ public class AAWallpapperPreviewController: AAViewController { self.fromName = false super.init() imageView.image = selectedImage - imageView.contentMode = .ScaleAspectFill + imageView.contentMode = .scaleAspectFill imageView.clipsToBounds = true cancelButton.backgroundColor = appStyle.vcPanelBgColor - cancelButton.addTarget(self, action: #selector(AAWallpapperPreviewController.cancelDidTap), forControlEvents: .TouchUpInside) - cancelButton.setTitle(AALocalized("AlertCancel"), forState: .Normal) - cancelButton.setTitleColor(appStyle.tabUnselectedTextColor, forState: .Normal) + cancelButton.addTarget(self, action: #selector(AAWallpapperPreviewController.cancelDidTap), for: .touchUpInside) + cancelButton.setTitle(AALocalized("AlertCancel"), for: UIControlState()) + cancelButton.setTitleColor(appStyle.tabUnselectedTextColor, for: UIControlState()) setButton.backgroundColor = appStyle.vcPanelBgColor - setButton.addTarget(self, action: #selector(AAWallpapperPreviewController.setDidTap), forControlEvents: .TouchUpInside) - setButton.setTitle(AALocalized("AlertSet"), forState: .Normal) - setButton.setTitleColor(appStyle.tabUnselectedTextColor, forState: .Normal) + setButton.addTarget(self, action: #selector(AAWallpapperPreviewController.setDidTap), for: .touchUpInside) + setButton.setTitle(AALocalized("AlertSet"), for: UIControlState()) + setButton.setTitleColor(appStyle.tabUnselectedTextColor, for: UIControlState()) } public required init(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - public override func viewDidLoad() { + open override func viewDidLoad() { super.viewDidLoad() - self.edgesForExtendedLayout = UIRectEdge.Top + self.edgesForExtendedLayout = UIRectEdge.top view.addSubview(imageView) view.addSubview(cancelButton) view.addSubview(setButton) } - public override func viewDidLayoutSubviews() { + open override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() imageView.frame = view.bounds @@ -74,11 +74,11 @@ public class AAWallpapperPreviewController: AAViewController { } func cancelDidTap() { - self.dismissViewControllerAnimated(true, completion: nil) + self.dismiss(animated: true, completion: nil) } func setDidTap() { - self.dismissViewControllerAnimated(true, completion: nil) + self.dismiss(animated: true, completion: nil) if self.fromName == true { Actor.changeSelectedWallpaper("local:\(imageName)") @@ -89,7 +89,7 @@ public class AAWallpapperPreviewController: AAViewController { let descriptor = "/tmp/customWallpaperImage" let path = CocoaFiles.pathFromDescriptor(descriptor) - UIImageJPEGRepresentation(self.selectedImage, 1.00)!.writeToFile(path, atomically: true) + try? UIImageJPEGRepresentation(self.selectedImage, 1.00)!.write(to: URL(fileURLWithPath: path), options: [.atomic]) Actor.changeSelectedWallpaper("file:\(descriptor)") }) @@ -97,4 +97,4 @@ public class AAWallpapperPreviewController: AAViewController { } } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/WebActions/AAWebActionController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/WebActions/AAWebActionController.swift index 4374e2ad10..faf0ebef25 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/WebActions/AAWebActionController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/WebActions/AAWebActionController.swift @@ -4,12 +4,12 @@ import Foundation -public class AAWebActionController: AAViewController, UIWebViewDelegate { +open class AAWebActionController: AAViewController, UIWebViewDelegate { - private var webView = UIWebView() + fileprivate var webView = UIWebView() - private let regex: AARegex - private let desc: ACWebActionDescriptor + fileprivate let regex: AARegex + fileprivate let desc: ACWebActionDescriptor public init(desc: ACWebActionDescriptor) { self.desc = desc @@ -21,29 +21,29 @@ public class AAWebActionController: AAViewController, UIWebViewDelegate { fatalError("init(coder:) has not been implemented") } - public override func viewDidLoad() { + open override func viewDidLoad() { super.viewDidLoad() webView.delegate = self view.addSubview(webView) - webView.loadRequest(NSURLRequest(URL: NSURL(string: desc.getUri())!)) + webView.loadRequest(URLRequest(url: URL(string: desc.getUri())!)) } - public override func viewDidLayoutSubviews() { + open override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() webView.frame = view.bounds } - public func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool { - if let url = request.URL { + open func webView(_ webView: UIWebView, shouldStartLoadWith request: URLRequest, navigationType: UIWebViewNavigationType) -> Bool { + if let url = request.url { let rawUrl = url.absoluteString // Match end url if regex.test(rawUrl) { - self.executeSafe(Actor.completeWebActionWithHash(desc.getActionHash(), withUrl: rawUrl)) { (val) -> Void in + self.executeSafe(Actor.completeWebAction(withHash: desc.getActionHash(), withUrl: rawUrl)) { (val) -> Void in self.dismiss() } return false @@ -51,4 +51,4 @@ public class AAWebActionController: AAViewController, UIWebViewDelegate { } return true } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Conversation/ConversationViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Conversation/ConversationViewController.swift index 9fe626f1ad..0783be8354 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Conversation/ConversationViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Conversation/ConversationViewController.swift @@ -8,7 +8,7 @@ import MobileCoreServices import AddressBook import AddressBookUI -public class ConversationViewController: +open class ConversationViewController: AAConversationContentController, UIDocumentMenuDelegate, UIDocumentPickerDelegate, @@ -21,7 +21,7 @@ public class ConversationViewController: AAStickersKeyboardDelegate { // Data binder - private let binder = AABinder() + fileprivate let binder = AABinder() // Internal state // Members for autocomplete @@ -34,38 +34,38 @@ public class ConversationViewController: // Views // - private let titleView: UILabel = UILabel() - private let subtitleView: UILabel = UILabel() - private let navigationView: UIView = UIView() - private let avatarView = AABarAvatarView() - private let backgroundView = UIImageView() - private var audioButton: UIButton = UIButton() - private var voiceRecorderView : AAVoiceRecorderView! - private let inputOverlay = UIView() - private let inputOverlayLabel = UILabel() + fileprivate let titleView: UILabel = UILabel() + fileprivate let subtitleView: UILabel = UILabel() + fileprivate let navigationView: UIView = UIView() + fileprivate let avatarView = AABarAvatarView() + fileprivate let backgroundView = UIImageView() + fileprivate var audioButton: UIButton = UIButton() + fileprivate var voiceRecorderView : AAVoiceRecorderView! + fileprivate let inputOverlay = UIView() + fileprivate let inputOverlayLabel = UILabel() // // Stickers // - private var stickersView: AAStickersKeyboard! - private var stickersButton : UIButton! - private var stickersOpen = false + fileprivate var stickersView: AAStickersKeyboard! + fileprivate var stickersButton : UIButton! + fileprivate var stickersOpen = false // // Audio Recorder // - public var audioRecorder: AAAudioRecorder! + open var audioRecorder: AAAudioRecorder! // // Mode // - private var textMode:Bool! - private var micOn: Bool! = true + fileprivate var textMode:Bool! + fileprivate var micOn: Bool! = true @@ -89,7 +89,7 @@ public class ConversationViewController: // backgroundView.clipsToBounds = true - backgroundView.contentMode = .ScaleAspectFill + backgroundView.contentMode = .scaleAspectFill backgroundView.backgroundColor = appStyle.chatBgColor // Custom background if available @@ -101,15 +101,15 @@ public class ConversationViewController: backgroundView.image = UIImage(contentsOfFile:path) } } - view.insertSubview(backgroundView, atIndex: 0) + view.insertSubview(backgroundView, at: 0) // // slk settings // self.bounces = false - self.keyboardPanningEnabled = true - self.registerPrefixesForAutoCompletion(["@"]) + self.isKeyboardPanningEnabled = true + self.registerPrefixes(forAutoCompletion: ["@"]) // @@ -117,22 +117,22 @@ public class ConversationViewController: // self.textInputbar.backgroundColor = appStyle.chatInputFieldBgColor self.textInputbar.autoHideRightButton = false; - self.textInputbar.translucent = false + self.textInputbar.isTranslucent = false // // Text view // self.textView.placeholder = AALocalized("ChatPlaceholder") - self.textView.keyboardAppearance = ActorSDK.sharedActor().style.isDarkApp ? .Dark : .Light + self.textView.keyboardAppearance = ActorSDK.sharedActor().style.isDarkApp ? .dark : .light // // Overlay // self.inputOverlay.addSubview(inputOverlayLabel) - self.inputOverlayLabel.textAlignment = .Center - self.inputOverlayLabel.font = UIFont.systemFontOfSize(18) + self.inputOverlayLabel.textAlignment = .center + self.inputOverlayLabel.font = UIFont.systemFont(ofSize: 18) self.inputOverlayLabel.textColor = ActorSDK.sharedActor().style.vcTintColor self.inputOverlay.viewDidTap = { self.onOverlayTap() @@ -141,44 +141,44 @@ public class ConversationViewController: // // Add stickers button // - self.stickersButton = UIButton(type: UIButtonType.System) - self.stickersButton.tintColor = UIColor.lightGrayColor().colorWithAlphaComponent(0.5) - self.stickersButton.setImage(UIImage.bundled("sticker_button"), forState: UIControlState.Normal) - self.stickersButton.addTarget(self, action: #selector(ConversationViewController.changeKeyboard), forControlEvents: UIControlEvents.TouchUpInside) + self.stickersButton = UIButton(type: UIButtonType.system) + self.stickersButton.tintColor = UIColor.lightGray.withAlphaComponent(0.5) + self.stickersButton.setImage(UIImage.bundled("sticker_button"), for: UIControlState()) + self.stickersButton.addTarget(self, action: #selector(ConversationViewController.changeKeyboard), for: UIControlEvents.touchUpInside) self.textInputbar.addSubview(stickersButton) // // Check text for set right button // - let checkText = Actor.loadDraftWithPeer(peer)!.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()) + let checkText = Actor.loadDraft(with: peer)!.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) if (checkText.isEmpty) { self.textMode = false self.rightButton.tintColor = appStyle.chatSendColor - self.rightButton.setImage(UIImage.tinted("aa_micbutton", color: appStyle.chatAttachColor), forState: UIControlState.Normal) - self.rightButton.setTitle("", forState: UIControlState.Normal) - self.rightButton.enabled = true + self.rightButton.setImage(UIImage.tinted("aa_micbutton", color: appStyle.chatAttachColor), for: UIControlState()) + self.rightButton.setTitle("", for: UIControlState()) + self.rightButton.isEnabled = true self.rightButton.layoutIfNeeded() - self.rightButton.addTarget(self, action: #selector(ConversationViewController.beginRecord(_:event:)), forControlEvents: UIControlEvents.TouchDown) - self.rightButton.addTarget(self, action: #selector(ConversationViewController.mayCancelRecord(_:event:)), forControlEvents: UIControlEvents.TouchDragInside.union(UIControlEvents.TouchDragOutside)) - self.rightButton.addTarget(self, action: #selector(ConversationViewController.finishRecord(_:event:)), forControlEvents: UIControlEvents.TouchUpInside.union(UIControlEvents.TouchCancel).union(UIControlEvents.TouchUpOutside)) + self.rightButton.addTarget(self, action: #selector(ConversationViewController.beginRecord(_:event:)), for: UIControlEvents.touchDown) + self.rightButton.addTarget(self, action: #selector(ConversationViewController.mayCancelRecord(_:event:)), for: UIControlEvents.touchDragInside.union(UIControlEvents.touchDragOutside)) + self.rightButton.addTarget(self, action: #selector(ConversationViewController.finishRecord(_:event:)), for: UIControlEvents.touchUpInside.union(UIControlEvents.touchCancel).union(UIControlEvents.touchUpOutside)) } else { self.textMode = true - self.stickersButton.hidden = true + self.stickersButton.isHidden = true - self.rightButton.setTitle(AALocalized("ChatSend"), forState: UIControlState.Normal) - self.rightButton.setTitleColor(appStyle.chatSendColor, forState: UIControlState.Normal) - self.rightButton.setTitleColor(appStyle.chatSendDisabledColor, forState: UIControlState.Disabled) - self.rightButton.setImage(nil, forState: UIControlState.Normal) - self.rightButton.enabled = true + self.rightButton.setTitle(AALocalized("ChatSend"), for: UIControlState()) + self.rightButton.setTitleColor(appStyle.chatSendColor, for: UIControlState()) + self.rightButton.setTitleColor(appStyle.chatSendDisabledColor, for: UIControlState.disabled) + self.rightButton.setImage(nil, for: UIControlState()) + self.rightButton.isEnabled = true self.rightButton.layoutIfNeeded() } @@ -190,28 +190,28 @@ public class ConversationViewController: self.audioRecorder = AAAudioRecorder() self.audioRecorder.delegate = self - self.leftButton.setImage(UIImage.tinted("conv_attach", color: appStyle.chatAttachColor), forState: UIControlState.Normal) + self.leftButton.setImage(UIImage.tinted("conv_attach", color: appStyle.chatAttachColor), for: UIControlState()) // // Navigation Title // - navigationView.frame = CGRectMake(0, 0, 200, 44) - navigationView.autoresizingMask = UIViewAutoresizing.FlexibleWidth + navigationView.frame = CGRect(x: 0, y: 0, width: 200, height: 44) + navigationView.autoresizingMask = UIViewAutoresizing.flexibleWidth titleView.font = UIFont.mediumSystemFontOfSize(17) titleView.adjustsFontSizeToFitWidth = false - titleView.textAlignment = NSTextAlignment.Center - titleView.lineBreakMode = NSLineBreakMode.ByTruncatingTail - titleView.autoresizingMask = UIViewAutoresizing.FlexibleWidth + titleView.textAlignment = NSTextAlignment.center + titleView.lineBreakMode = NSLineBreakMode.byTruncatingTail + titleView.autoresizingMask = UIViewAutoresizing.flexibleWidth titleView.textColor = appStyle.navigationTitleColor - subtitleView.font = UIFont.systemFontOfSize(13) + subtitleView.font = UIFont.systemFont(ofSize: 13) subtitleView.adjustsFontSizeToFitWidth = true - subtitleView.textAlignment = NSTextAlignment.Center - subtitleView.lineBreakMode = NSLineBreakMode.ByTruncatingTail - subtitleView.autoresizingMask = UIViewAutoresizing.FlexibleWidth + subtitleView.textAlignment = NSTextAlignment.center + subtitleView.lineBreakMode = NSLineBreakMode.byTruncatingTail + subtitleView.autoresizingMask = UIViewAutoresizing.flexibleWidth navigationView.addSubview(titleView) navigationView.addSubview(subtitleView) @@ -222,7 +222,7 @@ public class ConversationViewController: // // Navigation Avatar // - avatarView.frame = CGRectMake(0, 0, 40, 40) + avatarView.frame = CGRect(x: 0, y: 0, width: 40, height: 40) avatarView.viewDidTap = onAvatarTap let barItem = UIBarButtonItem(customView: avatarView) @@ -258,49 +258,49 @@ public class ConversationViewController: fatalError("init(coder:) has not been implemented") } - override public func viewDidLoad() { + override open func viewDidLoad() { super.viewDidLoad() - self.voiceRecorderView = AAVoiceRecorderView(frame: CGRectMake(0, 0, view.width - 30, 44)) - self.voiceRecorderView.hidden = true + self.voiceRecorderView = AAVoiceRecorderView(frame: CGRect(x: 0, y: 0, width: view.width - 30, height: 44)) + self.voiceRecorderView.isHidden = true self.voiceRecorderView.binedController = self self.textInputbar.addSubview(self.voiceRecorderView) - self.inputOverlay.backgroundColor = UIColor.whiteColor() - self.inputOverlay.hidden = false + self.inputOverlay.backgroundColor = UIColor.white + self.inputOverlay.isHidden = false self.textInputbar.addSubview(self.inputOverlay) - navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: UIBarButtonItemStyle.Plain, target: nil, action: nil) + navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: UIBarButtonItemStyle.plain, target: nil, action: nil) - let frame = CGRectMake(0, 0, self.view.frame.size.width, 216) + let frame = CGRect(x: 0, y: 0, width: self.view.frame.size.width, height: 216) self.stickersView = AAStickersKeyboard(frame: frame) self.stickersView.delegate = self - NSNotificationCenter.defaultCenter().addObserver( + NotificationCenter.default.addObserver( self, selector: #selector(ConversationViewController.updateStickersStateOnCloseKeyboard), - name: SLKKeyboardWillHideNotification, + name: NSNotification.Name.SLKKeyboardWillHide, object: nil) } - public override func viewDidLayoutSubviews() { + open override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() - self.stickersButton.frame = CGRectMake(self.view.frame.size.width-67, 12, 20, 20) - self.voiceRecorderView.frame = CGRectMake(0, 0, view.width - 30, 44) - self.inputOverlay.frame = CGRectMake(0, 0, view.width, 44) - self.inputOverlayLabel.frame = CGRectMake(0, 0, view.width, 44) + self.stickersButton.frame = CGRect(x: self.view.frame.size.width-67, y: 12, width: 20, height: 20) + self.voiceRecorderView.frame = CGRect(x: 0, y: 0, width: view.width - 30, height: 44) + self.inputOverlay.frame = CGRect(x: 0, y: 0, width: view.width, height: 44) + self.inputOverlayLabel.frame = CGRect(x: 0, y: 0, width: view.width, height: 44) } //////////////////////////////////////////////////////////// // MARK: - Lifecycle //////////////////////////////////////////////////////////// - override public func viewWillAppear(animated: Bool) { + override open func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) // Installing bindings - if (peer.peerType.ordinal() == ACPeerType.PRIVATE().ordinal()) { + if (peer.peerType.ordinal() == ACPeerType.private().ordinal()) { let user = Actor.getUserWithUid(peer.peerId) let nameModel = user.getNameModel() @@ -320,14 +320,14 @@ public class ConversationViewController: self.subtitleView.text = Actor.getFormatter().formatTyping() self.subtitleView.textColor = self.appStyle.navigationSubtitleActiveColor } else { - if (user.isBot().boolValue) { + if (user.isBot()) { self.subtitleView.text = "bot" self.subtitleView.textColor = self.appStyle.userOnlineNavigationColor } else { - let stateText = Actor.getFormatter().formatPresence(presence, withSex: user.getSex()) + let stateText = Actor.getFormatter().formatPresence(presence, with: user.getSex()) self.subtitleView.text = stateText; let state = presence!.state.ordinal() - if (state == ACUserPresence_State.ONLINE().ordinal()) { + if (state == ACUserPresence_State.online().ordinal()) { self.subtitleView.textColor = self.appStyle.userOnlineNavigationColor } else { self.subtitleView.textColor = self.appStyle.userOfflineNavigationColor @@ -336,8 +336,8 @@ public class ConversationViewController: } }) - self.inputOverlay.hidden = true - } else if (peer.peerType.ordinal() == ACPeerType.GROUP().ordinal()) { + self.inputOverlay.isHidden = true + } else if (peer.peerType.ordinal() == ACPeerType.group().ordinal()) { let group = Actor.getGroupWithGid(peer.peerId) let nameModel = group.getNameModel() @@ -348,7 +348,7 @@ public class ConversationViewController: binder.bind(group.getAvatarModel(), closure: { (value: ACAvatar?) -> () in self.avatarView.bind(group.getNameModel().get(), id: Int(group.getId()), avatar: value) }) - binder.bind(Actor.getGroupTypingWithGid(group.getId()), valueModel2: group.membersCount, valueModel3: group.getPresenceModel(), closure: { (typingValue:IOSIntArray?, membersCount: JavaLangInteger?, onlineCount:JavaLangInteger?) -> () in + binder.bind(Actor.getGroupTyping(withGid: group.getId()), valueModel2: group.membersCount, valueModel3: group.getPresenceModel(), closure: { (typingValue:IOSIntArray?, membersCount: JavaLangInteger?, onlineCount:JavaLangInteger?) -> () in if (!group.isMemberModel().get().booleanValue()) { self.subtitleView.text = AALocalized("ChatNoGroupAccess") self.subtitleView.textColor = self.appStyle.navigationSubtitleColor @@ -358,58 +358,58 @@ public class ConversationViewController: if (typingValue != nil && typingValue!.length() > 0) { self.subtitleView.textColor = self.appStyle.navigationSubtitleActiveColor if (typingValue!.length() == 1) { - let uid = typingValue!.intAtIndex(0); + let uid = typingValue!.int(at: 0); let user = Actor.getUserWithUid(uid) - self.subtitleView.text = Actor.getFormatter().formatTypingWithName(user.getNameModel().get()) + self.subtitleView.text = Actor.getFormatter().formatTyping(withName: user.getNameModel().get()) } else { - self.subtitleView.text = Actor.getFormatter().formatTypingWithCount(typingValue!.length()); + self.subtitleView.text = Actor.getFormatter().formatTyping(withCount: typingValue!.length()); } } else { var membersString = Actor.getFormatter().formatGroupMembers(membersCount!.intValue()) self.subtitleView.textColor = self.appStyle.navigationSubtitleColor - if (onlineCount == nil || onlineCount!.integerValue == 0) { + if (onlineCount == nil || onlineCount!.intValue == 0) { self.subtitleView.text = membersString; } else { - membersString = membersString + ", "; + membersString = membersString! + ", "; let onlineString = Actor.getFormatter().formatGroupOnline(onlineCount!.intValue()); - let attributedString = NSMutableAttributedString(string: (membersString + onlineString)) - attributedString.addAttribute(NSForegroundColorAttributeName, value: self.appStyle.userOnlineNavigationColor, range: NSMakeRange(membersString.length, onlineString.length)) + let attributedString = NSMutableAttributedString(string: (membersString! + onlineString!)) + attributedString.addAttribute(NSForegroundColorAttributeName, value: self.appStyle.userOnlineNavigationColor, range: NSMakeRange(membersString!.length, onlineString!.length)) self.subtitleView.attributedText = attributedString } } }) - binder.bind(group.isMember, valueModel2: group.isCanWriteMessage, valueModel3: group.isCanJoin, closure: { (isMember: JavaLangBoolean!, canWriteMessage: JavaLangBoolean!, canJoin: JavaLangBoolean!) in + binder.bind(group.isMember, valueModel2: group.isCanWriteMessage, valueModel3: group.isCanJoin, closure: { (isMember: JavaLangBoolean?, canWriteMessage: JavaLangBoolean?, canJoin: JavaLangBoolean?) in - if canWriteMessage.booleanValue() { - self.stickersButton.hidden = false - self.inputOverlay.hidden = true + if canWriteMessage!.booleanValue() { + self.stickersButton.isHidden = false + self.inputOverlay.isHidden = true } else { - if !isMember.booleanValue() { - if canJoin.booleanValue() { + if !isMember!.booleanValue() { + if canJoin!.booleanValue() { self.inputOverlayLabel.text = AALocalized("ChatJoin") } else { self.inputOverlayLabel.text = AALocalized("ChatNoGroupAccess") } } else { - if Actor.isNotificationsEnabledWithPeer(self.peer) { + if Actor.isNotificationsEnabled(with: self.peer) { self.inputOverlayLabel.text = AALocalized("ActionMute") } else { self.inputOverlayLabel.text = AALocalized("ActionUnmute") } } - self.stickersButton.hidden = true + self.stickersButton.isHidden = true self.stopAudioRecording() self.textInputbar.textView.text = "" - self.inputOverlay.hidden = false + self.inputOverlay.isHidden = false } }) - binder.bind(group.isDeleted) { (isDeleted: JavaLangBoolean!) in - if isDeleted.booleanValue() { + binder.bind(group.isDeleted) { (isDeleted: JavaLangBoolean?) in + if isDeleted!.booleanValue() { self.alertUser(AALocalized("ChatDeleted")) { - self.execute(Actor.deleteChatCommandWithPeer(self.peer), successBlock: { (r) in + self.execute(Actor.deleteChatCommand(with: self.peer), successBlock: { (r) in self.navigateBack() }) } @@ -417,33 +417,33 @@ public class ConversationViewController: } } - Actor.onConversationOpenWithPeer(peer) + Actor.onConversationOpen(with: peer) ActorSDK.sharedActor().trackPageVisible(content) - if textView.isFirstResponder() == false { + if textView.isFirstResponder == false { textView.resignFirstResponder() } - textView.text = Actor.loadDraftWithPeer(peer) + textView.text = Actor.loadDraft(with: peer) } - public func onOverlayTap() { + open func onOverlayTap() { if peer.isGroup { let group = Actor.getGroupWithGid(peer.peerId) if !group.isMember.get().booleanValue() { if group.isCanJoin.get().booleanValue() { - executePromise(Actor.joinGroupWithGid(peer.peerId)) + executePromise(Actor.joinGroup(withGid: peer.peerId)) } else { // DO NOTHING } } else if !group.isCanWriteMessage.get().booleanValue() { - if Actor.isNotificationsEnabledWithPeer(peer) { - Actor.changeNotificationsEnabledWithPeer(peer, withValue: false) + if Actor.isNotificationsEnabled(with: peer) { + Actor.changeNotificationsEnabled(with: peer, withValue: false) inputOverlayLabel.text = AALocalized("ActionUnmute") } else { - Actor.changeNotificationsEnabledWithPeer(peer, withValue: true) + Actor.changeNotificationsEnabled(with: peer, withValue: true) inputOverlayLabel.text = AALocalized("ActionMute") } } @@ -452,18 +452,18 @@ public class ConversationViewController: } } - override public func viewWillLayoutSubviews() { + override open func viewWillLayoutSubviews() { super.viewWillLayoutSubviews() backgroundView.frame = view.bounds - titleView.frame = CGRectMake(0, 4, (navigationView.frame.width - 0), 20) - subtitleView.frame = CGRectMake(0, 22, (navigationView.frame.width - 0), 20) + titleView.frame = CGRect(x: 0, y: 4, width: (navigationView.frame.width - 0), height: 20) + subtitleView.frame = CGRect(x: 0, y: 22, width: (navigationView.frame.width - 0), height: 20) - stickersView.frame = CGRectMake(0, 0, self.view.frame.size.width, 216) + stickersView.frame = CGRect(x: 0, y: 0, width: self.view.frame.size.width, height: 216) } - override public func viewDidAppear(animated: Bool) { + override open func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) if navigationController!.viewControllers.count > 2 { @@ -477,10 +477,10 @@ public class ConversationViewController: } } - override public func viewWillDisappear(animated: Bool) { + override open func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) - Actor.onConversationClosedWithPeer(peer) + Actor.onConversationClosed(with: peer) ActorSDK.sharedActor().trackPageHidden(content) if !AADevice.isiPad { @@ -491,10 +491,10 @@ public class ConversationViewController: self.textView.resignFirstResponder() } - override public func viewDidDisappear(animated: Bool) { + override open func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) - Actor.saveDraftWithPeer(peer, withDraft: textView.text) + Actor.saveDraft(with: peer, withDraft: textView.text) // Releasing bindings binder.unbindAll() @@ -507,12 +507,12 @@ public class ConversationViewController: func onAvatarTap() { let id = Int(peer.peerId) var controller: AAViewController! - if (peer.peerType.ordinal() == ACPeerType.PRIVATE().ordinal()) { + if (peer.peerType.ordinal() == ACPeerType.private().ordinal()) { controller = ActorSDK.sharedActor().delegate.actorControllerForUser(id) if controller == nil { controller = AAUserViewController(uid: id) } - } else if (peer.peerType.ordinal() == ACPeerType.GROUP().ordinal()) { + } else if (peer.peerType.ordinal() == ACPeerType.group().ordinal()) { controller = ActorSDK.sharedActor().delegate.actorControllerForGroup(id) if controller == nil { controller = AAGroupViewController(gid: id) @@ -526,8 +526,8 @@ public class ConversationViewController: navigation.viewControllers = [controller] let popover = UIPopoverController(contentViewController: navigation) controller.popover = popover - popover.presentPopoverFromBarButtonItem(navigationItem.rightBarButtonItem!, - permittedArrowDirections: UIPopoverArrowDirection.Up, + popover.present(from: navigationItem.rightBarButtonItem!, + permittedArrowDirections: UIPopoverArrowDirection.up, animated: true) } else { navigateNext(controller, removeCurrent: false) @@ -536,15 +536,15 @@ public class ConversationViewController: func onCallTap() { if (self.peer.isGroup) { - execute(ActorSDK.sharedActor().messenger.doCallWithGid(self.peer.peerId)) + execute(ActorSDK.sharedActor().messenger.doCall(withGid: self.peer.peerId)) } else if (self.peer.isPrivate) { - execute(ActorSDK.sharedActor().messenger.doCallWithUid(self.peer.peerId)) + execute(ActorSDK.sharedActor().messenger.doCall(withUid: self.peer.peerId)) } } func onVideoCallTap() { if (self.peer.isPrivate) { - execute(ActorSDK.sharedActor().messenger.doVideoCallWithUid(self.peer.peerId)) + execute(ActorSDK.sharedActor().messenger.doVideoCall(withUid: self.peer.peerId)) } } @@ -552,33 +552,33 @@ public class ConversationViewController: // MARK: - Text bar actions //////////////////////////////////////////////////////////// - override public func textDidUpdate(animated: Bool) { + override open func textDidUpdate(_ animated: Bool) { super.textDidUpdate(animated) - Actor.onTypingWithPeer(peer) + Actor.onTyping(with: peer) checkTextInTextView() } func checkTextInTextView() { - let text = self.textView.text.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()) - self.rightButton.enabled = true + let text = self.textView.text.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) + self.rightButton.isEnabled = true //change button's if !text.isEmpty && textMode == false { - self.rightButton.removeTarget(self, action: #selector(ConversationViewController.beginRecord(_:event:)), forControlEvents: UIControlEvents.TouchDown) - self.rightButton.removeTarget(self, action: #selector(ConversationViewController.mayCancelRecord(_:event:)), forControlEvents: UIControlEvents.TouchDragInside.union(UIControlEvents.TouchDragOutside)) - self.rightButton.removeTarget(self, action: #selector(ConversationViewController.finishRecord(_:event:)), forControlEvents: UIControlEvents.TouchUpInside.union(UIControlEvents.TouchCancel).union(UIControlEvents.TouchUpOutside)) + self.rightButton.removeTarget(self, action: #selector(ConversationViewController.beginRecord(_:event:)), for: UIControlEvents.touchDown) + self.rightButton.removeTarget(self, action: #selector(ConversationViewController.mayCancelRecord(_:event:)), for: UIControlEvents.touchDragInside.union(UIControlEvents.touchDragOutside)) + self.rightButton.removeTarget(self, action: #selector(ConversationViewController.finishRecord(_:event:)), for: UIControlEvents.touchUpInside.union(UIControlEvents.touchCancel).union(UIControlEvents.touchUpOutside)) self.rebindRightButton() - self.stickersButton.hidden = true + self.stickersButton.isHidden = true - self.rightButton.setTitle(AALocalized("ChatSend"), forState: UIControlState.Normal) - self.rightButton.setTitleColor(appStyle.chatSendColor, forState: UIControlState.Normal) - self.rightButton.setTitleColor(appStyle.chatSendDisabledColor, forState: UIControlState.Disabled) - self.rightButton.setImage(nil, forState: UIControlState.Normal) + self.rightButton.setTitle(AALocalized("ChatSend"), for: UIControlState()) + self.rightButton.setTitleColor(appStyle.chatSendColor, for: UIControlState()) + self.rightButton.setTitleColor(appStyle.chatSendDisabledColor, for: UIControlState.disabled) + self.rightButton.setImage(nil, for: UIControlState()) self.rightButton.layoutIfNeeded() self.textInputbar.layoutIfNeeded() @@ -587,17 +587,17 @@ public class ConversationViewController: } else if (text.isEmpty && textMode == true) { - self.rightButton.addTarget(self, action: #selector(ConversationViewController.beginRecord(_:event:)), forControlEvents: UIControlEvents.TouchDown) - self.rightButton.addTarget(self, action: #selector(ConversationViewController.mayCancelRecord(_:event:)), forControlEvents: UIControlEvents.TouchDragInside.union(UIControlEvents.TouchDragOutside)) - self.rightButton.addTarget(self, action: #selector(ConversationViewController.finishRecord(_:event:)), forControlEvents: UIControlEvents.TouchUpInside.union(UIControlEvents.TouchCancel).union(UIControlEvents.TouchUpOutside)) + self.rightButton.addTarget(self, action: #selector(ConversationViewController.beginRecord(_:event:)), for: UIControlEvents.touchDown) + self.rightButton.addTarget(self, action: #selector(ConversationViewController.mayCancelRecord(_:event:)), for: UIControlEvents.touchDragInside.union(UIControlEvents.touchDragOutside)) + self.rightButton.addTarget(self, action: #selector(ConversationViewController.finishRecord(_:event:)), for: UIControlEvents.touchUpInside.union(UIControlEvents.touchCancel).union(UIControlEvents.touchUpOutside)) - self.stickersButton.hidden = false + self.stickersButton.isHidden = false self.rightButton.tintColor = appStyle.chatAttachColor - self.rightButton.setImage(UIImage.bundled("aa_micbutton"), forState: UIControlState.Normal) - self.rightButton.setTitle("", forState: UIControlState.Normal) - self.rightButton.enabled = true + self.rightButton.setImage(UIImage.bundled("aa_micbutton"), for: UIControlState()) + self.rightButton.setTitle("", for: UIControlState()) + self.rightButton.isEnabled = true self.rightButton.layoutIfNeeded() @@ -613,14 +613,14 @@ public class ConversationViewController: // MARK: - Right/Left button pressed //////////////////////////////////////////////////////////// - override public func didPressRightButton(sender: AnyObject!) { + override open func didPressRightButton(_ sender: Any!) { if !self.textView.text.isEmpty { - Actor.sendMessageWithMentionsDetect(peer, withText: textView.text) + Actor.sendMessage(withMentionsDetect: peer, withText: textView.text) super.didPressRightButton(sender) } } - override public func didPressLeftButton(sender: AnyObject!) { + override open func didPressLeftButton(_ sender: Any!) { super.didPressLeftButton(sender) self.textInputbar.textView.resignFirstResponder() @@ -641,16 +641,16 @@ public class ConversationViewController: // MARK: - Completition //////////////////////////////////////////////////////////// - override public func didChangeAutoCompletionPrefix(prefix: String!, andWord word: String!) { - if self.peer.peerType.ordinal() == ACPeerType.GROUP().ordinal() { + override open func didChangeAutoCompletionPrefix(_ prefix: String!, andWord word: String!) { + if self.peer.peerType.ordinal() == ACPeerType.group().ordinal() { if prefix == "@" { let oldCount = filteredMembers.count - filteredMembers.removeAll(keepCapacity: true) + filteredMembers.removeAll(keepingCapacity: true) - let res = Actor.findMentionsWithGid(self.peer.peerId, withQuery: word) + let res = Actor.findMentions(withGid: self.peer.peerId, withQuery: word)! for index in 0.. Int { + override open func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return filteredMembers.count } - override public func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { - let res = AAAutoCompleteCell(style: UITableViewCellStyle.Default, reuseIdentifier: "user_name") - res.bindData(filteredMembers[indexPath.row], highlightWord: foundWord) + override open func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let res = AAAutoCompleteCell(style: UITableViewCellStyle.default, reuseIdentifier: "user_name") + res.bindData(filteredMembers[(indexPath as NSIndexPath).row], highlightWord: foundWord) return res } - override public func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { - let user = filteredMembers[indexPath.row] + override open func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let user = filteredMembers[(indexPath as NSIndexPath).row] var postfix = " " if foundPrefixRange.location == 0 { postfix = ": " } - acceptAutoCompletionWithString(user.mentionString + postfix, keepPrefix: !user.isNickname) + acceptAutoCompletion(with: user.mentionString + postfix, keepPrefix: !user.isNickname) } - override public func heightForAutoCompletionView() -> CGFloat { + override open func heightForAutoCompletionView() -> CGFloat { let cellHeight: CGFloat = 44.0; return cellHeight * CGFloat(filteredMembers.count) } - override public func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) { - cell.separatorInset = UIEdgeInsetsZero + override open func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { + cell.separatorInset = UIEdgeInsets.zero cell.preservesSuperviewLayoutMargins = false - cell.layoutMargins = UIEdgeInsetsZero + cell.layoutMargins = UIEdgeInsets.zero } //////////////////////////////////////////////////////////// // MARK: - Picker //////////////////////////////////////////////////////////// - public func actionSheetPickedImages(images:[(NSData,Bool)]) { + open func actionSheetPickedImages(_ images:[(Data,Bool)]) { for (i,j) in images { Actor.sendUIImage(i, peer: peer, animated:j) } } - public func actionSheetPickCamera() { - pickImage(.Camera) + open func actionSheetPickCamera() { + pickImage(.camera) } - public func actionSheetPickGallery() { - pickImage(.PhotoLibrary) + open func actionSheetPickGallery() { + pickImage(.photoLibrary) } - public func actionSheetCustomButton(index: Int) { + open func actionSheetCustomButton(_ index: Int) { if index == 0 { pickDocument() } else if index == 1 { @@ -733,62 +733,62 @@ public class ConversationViewController: } } - public func pickContact() { + open func pickContact() { let pickerController = ABPeoplePickerNavigationController() pickerController.peoplePickerDelegate = self - self.presentViewController(pickerController, animated: true, completion: nil) + self.present(pickerController, animated: true, completion: nil) } - public func pickLocation() { + open func pickLocation() { let pickerController = AALocationPickerController() pickerController.delegate = self - self.presentViewController(AANavigationController(rootViewController:pickerController), animated: true, completion: nil) + self.present(AANavigationController(rootViewController:pickerController), animated: true, completion: nil) } - public func pickDocument() { - let documentPicker = UIDocumentMenuViewController(documentTypes: UTTAll as! [String], inMode: UIDocumentPickerMode.Import) - documentPicker.view.backgroundColor = UIColor.clearColor() + open func pickDocument() { + let documentPicker = UIDocumentMenuViewController(documentTypes: UTTAll as [String], in: UIDocumentPickerMode.import) + documentPicker.view.backgroundColor = UIColor.clear documentPicker.delegate = self - self.presentViewController(documentPicker, animated: true, completion: nil) + self.present(documentPicker, animated: true, completion: nil) } //////////////////////////////////////////////////////////// // MARK: - Document picking //////////////////////////////////////////////////////////// - public func documentMenu(documentMenu: UIDocumentMenuViewController, didPickDocumentPicker documentPicker: UIDocumentPickerViewController) { + open func documentMenu(_ documentMenu: UIDocumentMenuViewController, didPickDocumentPicker documentPicker: UIDocumentPickerViewController) { documentPicker.delegate = self - self.presentViewController(documentPicker, animated: true, completion: nil) + self.present(documentPicker, animated: true, completion: nil) } - public func documentPicker(controller: UIDocumentPickerViewController, didPickDocumentAtURL url: NSURL) { + open func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentAt url: URL) { // Loading path and file name - let path = url.path! + let path = url.path let fileName = url.lastPathComponent // Check if file valid or directory var isDir : ObjCBool = false - if !NSFileManager.defaultManager().fileExistsAtPath(path, isDirectory: &isDir) { + if !FileManager.default.fileExists(atPath: path, isDirectory: &isDir) { // Not exists return } // Destination file - let descriptor = "/tmp/\(NSUUID().UUIDString)" + let descriptor = "/tmp/\(UUID().uuidString)" let destPath = CocoaFiles.pathFromDescriptor(descriptor) - if isDir { + if isDir.boolValue { // Zipping contents and sending execute(AATools.zipDirectoryCommand(path, to: destPath)) { (val) -> Void in - Actor.sendDocumentWithPeer(self.peer, withName: fileName, withMime: "application/zip", withDescriptor: descriptor) + Actor.sendDocument(with: self.peer, withName: fileName, withMime: "application/zip", withDescriptor: descriptor) } } else { // Sending file itself execute(AATools.copyFileCommand(path, to: destPath)) { (val) -> Void in - Actor.sendDocumentWithPeer(self.peer, withName: fileName, withMime: "application/octet-stream", withDescriptor: descriptor) + Actor.sendDocument(with: self.peer, withName: fileName, withMime: "application/octet-stream", withDescriptor: descriptor) } } } @@ -798,24 +798,24 @@ public class ConversationViewController: // MARK: - Image picking //////////////////////////////////////////////////////////// - func pickImage(source: UIImagePickerControllerSourceType) { + func pickImage(_ source: UIImagePickerControllerSourceType) { let pickerController = AAImagePickerController() pickerController.sourceType = source pickerController.mediaTypes = [kUTTypeImage as String,kUTTypeMovie as String] pickerController.delegate = self - self.presentViewController(pickerController, animated: true, completion: nil) + self.present(pickerController, animated: true, completion: nil) } - public func imagePickerController(picker: UIImagePickerController, didFinishPickingImage image: UIImage, editingInfo: [String : AnyObject]?) { - picker.dismissViewControllerAnimated(true, completion: nil) + open func imagePickerController(_ picker: UIImagePickerController, didFinishPickingImage image: UIImage, editingInfo: [String : AnyObject]?) { + picker.dismiss(animated: true, completion: nil) let imageData = UIImageJPEGRepresentation(image, 0.8) Actor.sendUIImage(imageData!, peer: peer, animated:false) } - public func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) { - picker.dismissViewControllerAnimated(true, completion: nil) + open func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) { + picker.dismiss(animated: true, completion: nil) if let image = info[UIImagePickerControllerOriginalImage] as? UIImage { let imageData = UIImageJPEGRepresentation(image, 0.8) @@ -823,37 +823,37 @@ public class ConversationViewController: Actor.sendUIImage(imageData!, peer: peer, animated:false) } else { - Actor.sendVideo(info[UIImagePickerControllerMediaURL] as! NSURL, peer: peer) + Actor.sendVideo(info[UIImagePickerControllerMediaURL] as! URL, peer: peer) } } - public func imagePickerControllerDidCancel(picker: UIImagePickerController) { - picker.dismissViewControllerAnimated(true, completion: nil) + open func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { + picker.dismiss(animated: true, completion: nil) } //////////////////////////////////////////////////////////// // MARK: - Location picking //////////////////////////////////////////////////////////// - public func locationPickerDidCancelled(controller: AALocationPickerController) { - controller.dismissViewControllerAnimated(true, completion: nil) + open func locationPickerDidCancelled(_ controller: AALocationPickerController) { + controller.dismiss(animated: true, completion: nil) } - public func locationPickerDidPicked(controller: AALocationPickerController, latitude: Double, longitude: Double) { - Actor.sendLocationWithPeer(self.peer, withLongitude: JavaLangDouble(double: longitude), withLatitude: JavaLangDouble(double: latitude), withStreet: nil, withPlace: nil) - controller.dismissViewControllerAnimated(true, completion: nil) + open func locationPickerDidPicked(_ controller: AALocationPickerController, latitude: Double, longitude: Double) { + Actor.sendLocation(with: self.peer, withLongitude: JavaLangDouble(value: longitude), withLatitude: JavaLangDouble(value: latitude), withStreet: nil, withPlace: nil) + controller.dismiss(animated: true, completion: nil) } //////////////////////////////////////////////////////////// // MARK: - Contact picking //////////////////////////////////////////////////////////// - public func peoplePickerNavigationController(peoplePicker: ABPeoplePickerNavigationController, didSelectPerson person: ABRecord) { + open func peoplePickerNavigationController(_ peoplePicker: ABPeoplePickerNavigationController, didSelectPerson person: ABRecord) { // Dismissing picker - peoplePicker.dismissViewControllerAnimated(true, completion: nil) + peoplePicker.dismiss(animated: true, completion: nil) // Names @@ -865,38 +865,38 @@ public class ConversationViewController: let hasAvatarImage = ABPersonHasImageData(person) if (hasAvatarImage) { let imgData = ABPersonCopyImageDataWithFormat(person, kABPersonImageFormatOriginalSize).takeRetainedValue() - let image = UIImage(data: imgData)?.resizeSquare(90, maxH: 90) + let image = UIImage(data: imgData as Data)?.resizeSquare(90, maxH: 90) if (image != nil) { let thumbData = UIImageJPEGRepresentation(image!, 0.55) - jAvatarImage = thumbData?.base64EncodedStringWithOptions(NSDataBase64EncodingOptions()) + jAvatarImage = thumbData?.base64EncodedString(options: NSData.Base64EncodingOptions()) } } // Phones let jPhones = JavaUtilArrayList() - let phoneNumbers: ABMultiValueRef = ABRecordCopyValue(person, kABPersonPhoneProperty).takeRetainedValue() + let phoneNumbers: ABMultiValue = ABRecordCopyValue(person, kABPersonPhoneProperty).takeRetainedValue() let phoneCount = ABMultiValueGetCount(phoneNumbers) for i in 0 ..< phoneCount { let phone = (ABMultiValueCopyValueAtIndex(phoneNumbers, i).takeRetainedValue() as! String).trim() - jPhones.addWithId(phone) + jPhones?.add(withId: phone) } // Email let jEmails = JavaUtilArrayList() - let emails: ABMultiValueRef = ABRecordCopyValue(person, kABPersonEmailProperty).takeRetainedValue() + let emails: ABMultiValue = ABRecordCopyValue(person, kABPersonEmailProperty).takeRetainedValue() let emailsCount = ABMultiValueGetCount(emails) for i in 0 ..< emailsCount { let email = (ABMultiValueCopyValueAtIndex(emails, i).takeRetainedValue() as! String).trim() if (email.length > 0) { - jEmails.addWithId(email) + jEmails?.add(withId: email) } } // Sending - Actor.sendContactWithPeer(self.peer, withName: name!, withPhones: jPhones, withEmails: jEmails, withPhoto: jAvatarImage) + Actor.sendContact(with: self.peer, withName: name!, withPhones: jPhones!, withEmails: jEmails!, withPhoto: jAvatarImage) } @@ -921,25 +921,25 @@ public class ConversationViewController: func onAudioRecordingFinished() { print("onAudioRecordingFinished\n") - audioRecorder.finish { (path: String!, duration: NSTimeInterval) -> Void in + audioRecorder.finish { (path: String?, duration: TimeInterval) -> Void in if (nil == path) { print("onAudioRecordingFinished: empty path") return } - NSLog("onAudioRecordingFinished: %@ [%lfs]", path, duration) - let range = path.rangeOfString("/tmp", options: NSStringCompareOptions(), range: nil, locale: nil) - let descriptor = path.substringFromIndex(range!.startIndex) + NSLog("onAudioRecordingFinished: %@ [%lfs]", path!, duration) + let range = path!.range(of: "/tmp", options: NSString.CompareOptions(), range: nil, locale: nil) + let descriptor = path!.substring(from: range!.lowerBound) NSLog("Audio Recording file: \(descriptor)") - Actor.sendAudioWithPeer(self.peer, withName: NSString.localizedStringWithFormat("%@.ogg", NSUUID().UUIDString) as String, + Actor.sendAudio(with: self.peer, withName: NSString.localizedStringWithFormat("%@.ogg", UUID().uuidString) as String, withDuration: jint(duration*1000), withDescriptor: descriptor) } audioRecorder.cancel() } - public func audioRecorderDidStartRecording() { + open func audioRecorderDidStartRecording() { self.voiceRecorderView.recordingStarted() } @@ -955,16 +955,16 @@ public class ConversationViewController: } } - func beginRecord(button:UIButton,event:UIEvent) { + func beginRecord(_ button:UIButton,event:UIEvent) { self.voiceRecorderView.startAnimation() - self.voiceRecorderView.hidden = false - self.stickersButton.hidden = true + self.voiceRecorderView.isHidden = false + self.stickersButton.isHidden = true - let touches : Set = event.touchesForView(button)! + let touches : Set = event.touches(for: button)! let touch = touches.first! - let location = touch.locationInView(button) + let location = touch.location(in: button) self.voiceRecorderView.trackTouchPoint = location self.voiceRecorderView.firstTouchPoint = location @@ -973,11 +973,11 @@ public class ConversationViewController: self.onAudioRecordingStarted() } - func mayCancelRecord(button:UIButton,event:UIEvent) { + func mayCancelRecord(_ button:UIButton,event:UIEvent) { - let touches : Set = event.touchesForView(button)! + let touches : Set = event.touches(for: button)! let touch = touches.first! - let currentLocation = touch.locationInView(button) + let currentLocation = touch.location(in: button) if (currentLocation.x < self.rightButton.frame.origin.x) { @@ -994,11 +994,11 @@ public class ConversationViewController: if ((self.voiceRecorderView.firstTouchPoint.x - self.voiceRecorderView.trackTouchPoint.x) > 120) { //cancel - self.voiceRecorderView.hidden = true - self.stickersButton.hidden = false + self.voiceRecorderView.isHidden = true + self.stickersButton.isHidden = false self.stopAudioRecording() self.voiceRecorderView.recordingStoped() - button.cancelTrackingWithEvent(event) + button.cancelTracking(with: event) closeRecorderAnimation() @@ -1017,7 +1017,7 @@ public class ConversationViewController: let stickerViewFrame = self.stickersButton.frame stickersButton.frame.origin.x = self.stickersButton.frame.origin.x + 500 - UIView.animateWithDuration(1.5, delay: 0.0, usingSpringWithDamping: 0.5, initialSpringVelocity: 1.0, options: UIViewAnimationOptions.CurveLinear, animations: { () -> Void in + UIView.animate(withDuration: 1.5, delay: 0.0, usingSpringWithDamping: 0.5, initialSpringVelocity: 1.0, options: UIViewAnimationOptions.curveLinear, animations: { () -> Void in self.leftButton.frame = leftButtonFrame self.textView.frame = textViewFrame @@ -1031,10 +1031,10 @@ public class ConversationViewController: } - func finishRecord(button:UIButton,event:UIEvent) { + func finishRecord(_ button:UIButton,event:UIEvent) { closeRecorderAnimation() - self.voiceRecorderView.hidden = true - self.stickersButton.hidden = false + self.voiceRecorderView.isHidden = true + self.stickersButton.isHidden = false self.onAudioRecordingFinished() self.voiceRecorderView.recordingStoped() } @@ -1045,7 +1045,7 @@ public class ConversationViewController: func updateStickersStateOnCloseKeyboard() { self.stickersOpen = false - self.stickersButton.setImage(UIImage.bundled("sticker_button"), forState: UIControlState.Normal) + self.stickersButton.setImage(UIImage.bundled("sticker_button"), for: UIControlState()) self.textInputbar.textView.inputView = nil } @@ -1054,13 +1054,13 @@ public class ConversationViewController: // self.stickersView.loadStickers() self.textInputbar.textView.inputView = self.stickersView - self.textInputbar.textView.inputView?.opaque = false - self.textInputbar.textView.inputView?.backgroundColor = UIColor.clearColor() + self.textInputbar.textView.inputView?.isOpaque = false + self.textInputbar.textView.inputView?.backgroundColor = UIColor.clear self.textInputbar.textView.refreshFirstResponder() self.textInputbar.textView.refreshInputViews() self.textInputbar.textView.becomeFirstResponder() - self.stickersButton.setImage(UIImage.bundled("keyboard_button"), forState: UIControlState.Normal) + self.stickersButton.setImage(UIImage.bundled("keyboard_button"), for: UIControlState()) self.stickersOpen = true } else { @@ -1070,7 +1070,7 @@ public class ConversationViewController: self.textInputbar.textView.refreshInputViews() self.textInputbar.textView.becomeFirstResponder() - self.stickersButton.setImage(UIImage.bundled("sticker_button"), forState: UIControlState.Normal) + self.stickersButton.setImage(UIImage.bundled("sticker_button"), for: UIControlState()) self.stickersOpen = false } @@ -1078,8 +1078,8 @@ public class ConversationViewController: self.view.layoutIfNeeded() } - public func stickerDidSelected(keyboard: AAStickersKeyboard, sticker: ACSticker) { - Actor.sendStickerWithPeer(self.peer, withSticker: sticker) + open func stickerDidSelected(_ keyboard: AAStickersKeyboard, sticker: ACSticker) { + Actor.sendSticker(with: self.peer, with: sticker) } } @@ -1093,7 +1093,7 @@ class AABarAvatarView : AAAvatarView { // fatalError("init(coder:) has not been implemented") // } - override func alignmentRectInsets() -> UIEdgeInsets { + override var alignmentRectInsets : UIEdgeInsets { return UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 8) } } @@ -1107,7 +1107,7 @@ class AACallButton: UIImageView { fatalError("init(coder:) has not been implemented") } - override func alignmentRectInsets() -> UIEdgeInsets { + override var alignmentRectInsets : UIEdgeInsets { return UIEdgeInsets(top: 0, left: -2, bottom: 0, right: 0) } } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAAddParticipantViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAAddParticipantViewController.swift index ffb5491e76..550868c235 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAAddParticipantViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAAddParticipantViewController.swift @@ -4,7 +4,7 @@ import UIKit -public class AAAddParticipantViewController: AAContactsListContentController, AAContactsListContentControllerDelegate { +open class AAAddParticipantViewController: AAContactsListContentController, AAContactsListContentControllerDelegate { public init (gid: Int) { super.init() @@ -17,18 +17,18 @@ public class AAAddParticipantViewController: AAContactsListContentController, AA fatalError("init(coder:) has not been implemented") } - public override func viewDidLoad() { + open override func viewDidLoad() { super.viewDidLoad() title = AALocalized("GroupAddParticipantTitle") - navigationItem.leftBarButtonItem = UIBarButtonItem( - title: AALocalized("NavigationCancel"), - style: UIBarButtonItemStyle.Plain, - target: self, action: #selector(AAViewController.dismiss)) +// navigationItem.leftBarButtonItem = UIBarButtonItem( +// title: AALocalized("NavigationCancel"), +// style: UIBarButtonItemStyle.plain, +// target: self, action: #selector(AAViewController.dismiss)) } - public func willAddContacts(controller: AAContactsListContentController, section: AAManagedSection) { + open func willAddContacts(_ controller: AAContactsListContentController, section: AAManagedSection) { if group.isCanInviteViaLink.get().booleanValue() { section.custom { (r:AACustomRow) -> () in r.height = 56 @@ -43,22 +43,22 @@ public class AAAddParticipantViewController: AAContactsListContentController, AA } } - public func contactDidBind(controller: AAContactsListContentController, contact: ACContact, cell: AAContactCell) { + open func contactDidBind(_ controller: AAContactsListContentController, contact: ACContact, cell: AAContactCell) { cell.bindDisabled(isAlreadyMember(contact.uid)) } - public func contactDidTap(controller: AAContactsListContentController, contact: ACContact) -> Bool { + open func contactDidTap(_ controller: AAContactsListContentController, contact: ACContact) -> Bool { if !isAlreadyMember(contact.uid) { - self.executeSafeOnlySuccess(Actor.inviteMemberCommandWithGid(jint(gid), withUid: jint(contact.uid))) { (val) -> () in + self.executeSafeOnlySuccess(Actor.inviteMemberCommand(withGid: jint(gid), withUid: jint(contact.uid))) { (val) -> () in self.dismiss() } } return true } - public func isAlreadyMember(uid: jint) -> Bool { - let members: [ACGroupMember] = group.getMembersModel().get().toArray().toSwiftArray() + open func isAlreadyMember(_ uid: jint) -> Bool { + let members: [ACGroupMember] = (group.getMembersModel().get() as AnyObject).toArray().toSwiftArray() for m in members { if m.uid == uid { return true diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupAdministrationViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupAdministrationViewController.swift index 898d6c5a8f..39a815f10c 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupAdministrationViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupAdministrationViewController.swift @@ -4,16 +4,16 @@ import Foundation -public class AAGroupAdministrationViewController: AAContentTableController { +open class AAGroupAdministrationViewController: AAContentTableController { - private var isChannel: Bool = false - private var shortNameRow: AACommonRow! - private var shareHistoryRow: AACommonRow! + fileprivate var isChannel: Bool = false + fileprivate var shortNameRow: AACommonRow! + fileprivate var shareHistoryRow: AACommonRow! public init(gid: Int) { - super.init(style: .SettingsGrouped) + super.init(style: .settingsGrouped) self.gid = gid - self.isChannel = group.groupType == ACGroupType.CHANNEL() + self.isChannel = group.groupType == ACGroupType.channel() navigationItem.title = AALocalized("GroupAdministration") } @@ -21,7 +21,7 @@ public class AAGroupAdministrationViewController: AAContentTableController { fatalError("init(coder:) has not been implemented") } - public override func tableDidLoad() { + open override func tableDidLoad() { section { (s) in if isChannel { @@ -54,7 +54,7 @@ public class AAGroupAdministrationViewController: AAContentTableController { } if group.isCanEditAdministration.get().booleanValue() { - r.style = .Navigation + r.style = .navigation r.selectAction = { () -> Bool in self.navigateNext(AAGroupTypeViewController(gid: self.gid, isCreation: false)) return false @@ -76,7 +76,7 @@ public class AAGroupAdministrationViewController: AAContentTableController { r.hint = nil r.selectAction = { () -> Bool in self.confirmAlertUser("GroupShareMessage", action: "GroupShareAction", tapYes: { - self.executePromise(Actor.shareHistoryWithGid(jint(self.gid))) + self.executePromise(Actor.shareHistory(withGid: jint(self.gid))) }) return true } @@ -99,7 +99,7 @@ public class AAGroupAdministrationViewController: AAContentTableController { s.danger(action, closure: { (r) in r.selectAction = { () -> Bool in self.confirmAlertUserDanger(self.isChannel ? "ActionDeleteChannelMessage" : "ActionDeleteGroupMessage", action: "ActionDelete", tapYes: { - self.executePromise(Actor.deleteGroupWithGid(jint(self.gid))).after { + self.executePromise(Actor.deleteGroup(withGid: jint(self.gid))).after { let first = self.navigationController!.viewControllers.first! self.navigationController!.setViewControllers([first], animated: true) } @@ -111,18 +111,18 @@ public class AAGroupAdministrationViewController: AAContentTableController { } } - public override func tableWillBind(binder: AABinder) { + open override func tableWillBind(_ binder: AABinder) { - binder.bind(self.group.shortName) { (value: String!) in + binder.bind(self.group.shortName) { (value: String?) in if let row = self.shortNameRow { row.reload() } } - binder.bind(self.group.isHistoryShared) { (value: JavaLangBoolean!) in + binder.bind(self.group.isHistoryShared) { (value: JavaLangBoolean?) in if let row = self.shareHistoryRow { row.reload() } } } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupEditInfoViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupEditInfoViewController.swift index d10b916f0d..53ed6c0ac0 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupEditInfoViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupEditInfoViewController.swift @@ -5,31 +5,31 @@ import Foundation import SZTextView -public class AAGroupEditInfoController: AAViewController, UITextViewDelegate { +open class AAGroupEditInfoController: AAViewController, UITextViewDelegate { - private var isChannel = false - private let scrollView = UIScrollView() - private let bgContainer = UIView() - private let topSeparator = UIView() - private let bottomSeparator = UIView() + fileprivate var isChannel = false + fileprivate let scrollView = UIScrollView() + fileprivate let bgContainer = UIView() + fileprivate let topSeparator = UIView() + fileprivate let bottomSeparator = UIView() - private let avatarView = AAAvatarView() - private let nameInput = UITextField() - private let nameInputSeparator = UIView() - private let descriptionView = SZTextView() - private let descriptionSeparator = UIView() + fileprivate let avatarView = AAAvatarView() + fileprivate let nameInput = UITextField() + fileprivate let nameInputSeparator = UIView() + fileprivate let descriptionView = SZTextView() + fileprivate let descriptionSeparator = UIView() public init(gid: Int) { super.init() self.gid = gid - self.isChannel = group.groupType == ACGroupType.CHANNEL() + self.isChannel = group.groupType == ACGroupType.channel() } public required init(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - public override func viewDidLoad() { + open override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = appStyle.vcBackyardColor @@ -58,7 +58,7 @@ public class AAGroupEditInfoController: AAViewController, UITextViewDelegate { self.avatarDidTap() } - nameInput.font = UIFont.systemFontOfSize(19) + nameInput.font = UIFont.systemFont(ofSize: 19) if isChannel { nameInput.placeholder = AALocalized("GroupEditNameChannel") } else { @@ -67,37 +67,37 @@ public class AAGroupEditInfoController: AAViewController, UITextViewDelegate { nameInput.text = group.name.get() descriptionView.delegate = self - descriptionView.font = UIFont.systemFontOfSize(17) + descriptionView.font = UIFont.systemFont(ofSize: 17) descriptionView.placeholder = AALocalized("GroupEditDescription") descriptionView.text = group.about.get() - descriptionView.scrollEnabled = false + descriptionView.isScrollEnabled = false if isChannel { navigationItem.title = AALocalized("GroupEditTitleChannel") } else { navigationItem.title = AALocalized("GroupEditTitle") } - navigationItem.rightBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationSave"), style: .Done, target: self, action: #selector(saveDidPressed)) - navigationItem.leftBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationCancel"), style: .Done, target: self, action: #selector(cancelEdit)) + navigationItem.rightBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationSave"), style: .done, target: self, action: #selector(saveDidPressed)) + navigationItem.leftBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationCancel"), style: .done, target: self, action: #selector(cancelEdit)) } - public override func viewWillLayoutSubviews() { + open override func viewWillLayoutSubviews() { super.viewWillLayoutSubviews() - scrollView.frame = CGRectMake(0, 0, view.width, view.height) + scrollView.frame = CGRect(x: 0, y: 0, width: view.width, height: view.height) - nameInput.frame = CGRectMake(94, 34, view.width - 94 - 10, 24) - nameInputSeparator.frame = CGRectMake(94, 34 + 24, view.width - 94, 0.5) - avatarView.frame = CGRectMake(14, 14, 66, 66) + nameInput.frame = CGRect(x: 94, y: 34, width: view.width - 94 - 10, height: 24) + nameInputSeparator.frame = CGRect(x: 94, y: 34 + 24, width: view.width - 94, height: 0.5) + avatarView.frame = CGRect(x: 14, y: 14, width: 66, height: 66) - descriptionSeparator.frame = CGRectMake(0, 94, view.width, 0.5) - topSeparator.frame = CGRectMake(0, 0, view.width, 0.5) + descriptionSeparator.frame = CGRect(x: 0, y: 94, width: view.width, height: 0.5) + topSeparator.frame = CGRect(x: 0, y: 0, width: view.width, height: 0.5) layoutContainer() } - public func avatarDidTap() { - let hasCamera = UIImagePickerController.isSourceTypeAvailable(UIImagePickerControllerSourceType.Camera) + open func avatarDidTap() { + let hasCamera = UIImagePickerController.isSourceTypeAvailable(UIImagePickerControllerSourceType.camera) self.showActionSheet( hasCamera ? ["PhotoCamera", "PhotoLibrary"] : ["PhotoLibrary"], cancelButton: "AlertCancel", destructButton: self.group.getAvatarModel().get() != nil ? "PhotoRemove" : nil, @@ -108,7 +108,7 @@ public class AAGroupEditInfoController: AAViewController, UITextViewDelegate { self.confirmAlertUser("PhotoRemoveGroupMessage", action: "PhotoRemove", tapYes: { () -> () in - Actor.removeGroupAvatarWithGid(jint(self.gid)) + Actor.removeGroupAvatar(withGid: jint(self.gid)) self.avatarView.bind(self.group.name.get(), id: self.gid, avatar: nil) }, tapNo: nil) } else if (index >= 0) { @@ -121,15 +121,15 @@ public class AAGroupEditInfoController: AAViewController, UITextViewDelegate { }) } - public func saveDidPressed() { + open func saveDidPressed() { let text = nameInput.text!.trim() let about = self.descriptionView.text!.trim() nameInput.resignFirstResponder() descriptionView.resignFirstResponder() if text != group.name.get() { - executePromise(Actor.editGroupTitleWithGid(jint(gid), withTitle: text).then({ (v: ARVoid!) in + executePromise(Actor.editGroupTitle(withGid: jint(gid), withTitle: text).then({ (v: ARVoid!) in if about != self.group.about.get() { - self.executePromise(Actor.editGroupAboutWithGid(jint(self.gid), withAbout: about).then({ (v: ARVoid!) in + self.executePromise(Actor.editGroupAbout(withGid: jint(self.gid), withAbout: about).then({ (v: ARVoid!) in self.cancelEdit() })) } else { @@ -138,7 +138,7 @@ public class AAGroupEditInfoController: AAViewController, UITextViewDelegate { })) } else { if about != self.group.about.get() { - self.executePromise(Actor.editGroupAboutWithGid(jint(self.gid), withAbout: about).then({ (v: ARVoid!) in + self.executePromise(Actor.editGroupAbout(withGid: jint(self.gid), withAbout: about).then({ (v: ARVoid!) in self.cancelEdit() })) } else { @@ -153,14 +153,14 @@ public class AAGroupEditInfoController: AAViewController, UITextViewDelegate { dismiss() } - public func textViewDidChange(textView: UITextView) { + open func textViewDidChange(_ textView: UITextView) { layoutContainer() } - private func layoutContainer() { - let newSize = descriptionView.sizeThatFits(CGSize(width: view.width - 20, height: CGFloat.max)) - descriptionView.frame = CGRectMake(10, 102, view.width - 20, max(newSize.height, 33)) - bgContainer.frame = CGRectMake(0, 0, view.width, 100 + descriptionView.height + 8) - bottomSeparator.frame = CGRectMake(0, bgContainer.height - 0.5, bgContainer.width, 0.5) + fileprivate func layoutContainer() { + let newSize = descriptionView.sizeThatFits(CGSize(width: view.width - 20, height: CGFloat.greatestFiniteMagnitude)) + descriptionView.frame = CGRect(x: 10, y: 102, width: view.width - 20, height: max(newSize.height, 33)) + bgContainer.frame = CGRect(x: 0, y: 0, width: view.width, height: 100 + descriptionView.height + 8) + bottomSeparator.frame = CGRect(x: 0, y: bgContainer.height - 0.5, width: bgContainer.width, height: 0.5) } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupTypeController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupTypeController.swift index 88b13d0665..2f5734d454 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupTypeController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupTypeController.swift @@ -4,30 +4,30 @@ import Foundation -public class AAGroupTypeViewController: AAContentTableController { +open class AAGroupTypeViewController: AAContentTableController { - private let isCreation: Bool - private var isChannel: Bool = false - private var isPublic: Bool = false - private var linkSection: AAManagedSection! - private var publicRow: AACommonRow! - private var privateRow: AACommonRow! - private var shortNameRow: AAEditRow! + fileprivate let isCreation: Bool + fileprivate var isChannel: Bool = false + fileprivate var isPublic: Bool = false + fileprivate var linkSection: AAManagedSection! + fileprivate var publicRow: AACommonRow! + fileprivate var privateRow: AACommonRow! + fileprivate var shortNameRow: AAEditRow! public init(gid: Int, isCreation: Bool) { self.isCreation = isCreation - super.init(style: .SettingsGrouped) + super.init(style: .settingsGrouped) self.gid = gid - self.isChannel = group.groupType == ACGroupType.CHANNEL() + self.isChannel = group.groupType == ACGroupType.channel() if (isChannel) { navigationItem.title = AALocalized("GroupTypeTitleChannel") } else { navigationItem.title = AALocalized("GroupTypeTitle") } if isCreation { - navigationItem.rightBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationNext"), style: .Done, target: self, action: #selector(saveDidTap)) + navigationItem.rightBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationNext"), style: .done, target: self, action: #selector(saveDidTap)) } else { - navigationItem.rightBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationSave"), style: .Done, target: self, action: #selector(saveDidTap)) + navigationItem.rightBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationSave"), style: .done, target: self, action: #selector(saveDidTap)) } } @@ -35,21 +35,21 @@ public class AAGroupTypeViewController: AAContentTableController { fatalError("init(coder:) has not been implemented") } - public override func tableDidLoad() { + open override func tableDidLoad() { self.isPublic = group.shortName.get() != nil section { (s) in if isChannel { - s.headerText = AALocalized("GroupTypeTitleChannel").uppercaseString + s.headerText = AALocalized("GroupTypeTitleChannel").uppercased() if self.isPublic { s.footerText = AALocalized("GroupTypeHintPublicChannel") } else { s.footerText = AALocalized("GroupTypeHintPrivateChannel") } } else { - s.headerText = AALocalized("GroupTypeTitle").uppercaseString + s.headerText = AALocalized("GroupTypeTitle").uppercased() if self.isPublic { s.footerText = AALocalized("GroupTypeHintPublic") } else { @@ -73,17 +73,17 @@ public class AAGroupTypeViewController: AAContentTableController { } else { s.footerText = AALocalized("GroupTypeHintPublic") } - self.tableView.reloadSection(0, withRowAnimation: .Automatic) + self.tableView.reloadSection(0, with: .automatic) self.managedTable.sections.append(self.linkSection) - self.tableView.insertSection(1, withRowAnimation: .Fade) + self.tableView.insertSection(1, with: .fade) } return true } r.bindAction = { (r) in if self.isPublic { - r.style = .Checkmark + r.style = .checkmark } else { - r.style = .Normal + r.style = .normal } } }) @@ -105,17 +105,17 @@ public class AAGroupTypeViewController: AAContentTableController { } else { s.footerText = AALocalized("GroupTypeHintPrivate") } - self.tableView.reloadSection(0, withRowAnimation: .Automatic) - self.managedTable.sections.removeAtIndex(1) - self.tableView.deleteSection(1, withRowAnimation: .Fade) + self.tableView.reloadSection(0, with: .automatic) + self.managedTable.sections.remove(at: 1) + self.tableView.deleteSection(1, with: .fade) } return true } r.bindAction = { (r) in if !self.isPublic { - r.style = .Checkmark + r.style = .checkmark } else { - r.style = .Normal + r.style = .normal } } }) @@ -129,17 +129,17 @@ public class AAGroupTypeViewController: AAContentTableController { } self.shortNameRow = s.edit({ (r) in - r.autocapitalizationType = .None + r.autocapitalizationType = .none r.prefix = ActorSDK.sharedActor().invitePrefixShort r.text = self.group.shortName.get() }) } if !self.isPublic { - managedTable.sections.removeAtIndex(1) + managedTable.sections.remove(at: 1) } } - public func saveDidTap() { + open func saveDidTap() { let nShortName: String? if self.isPublic { if let shortNameVal = self.shortNameRow.text?.trim() { @@ -156,12 +156,12 @@ public class AAGroupTypeViewController: AAContentTableController { } if nShortName != group.shortName.get() { - executePromise(Actor.editGroupShortNameWithGid(jint(self.gid), withAbout: nShortName).then({ (r:ARVoid!) in + executePromise(Actor.editGroupShortName(withGid: jint(self.gid), withAbout: nShortName).then({ (r:ARVoid!) in if (self.isCreation) { - if let customController = ActorSDK.sharedActor().delegate.actorControllerForConversation(ACPeer.groupWithInt(jint(self.gid))) { + if let customController = ActorSDK.sharedActor().delegate.actorControllerForConversation(ACPeer.group(with: jint(self.gid))) { self.navigateDetail(customController) } else { - self.navigateDetail(ConversationViewController(peer: ACPeer.groupWithInt(jint(self.gid)))) + self.navigateDetail(ConversationViewController(peer: ACPeer.group(with: jint(self.gid)))) } self.dismiss() } else { @@ -170,10 +170,10 @@ public class AAGroupTypeViewController: AAContentTableController { })) } else { if (isCreation) { - if let customController = ActorSDK.sharedActor().delegate.actorControllerForConversation(ACPeer.groupWithInt(jint(self.gid))) { + if let customController = ActorSDK.sharedActor().delegate.actorControllerForConversation(ACPeer.group(with: jint(self.gid))) { self.navigateDetail(customController) } else { - self.navigateDetail(ConversationViewController(peer: ACPeer.groupWithInt(jint(self.gid)))) + self.navigateDetail(ConversationViewController(peer: ACPeer.group(with: jint(self.gid)))) } self.dismiss() } else { @@ -181,4 +181,4 @@ public class AAGroupTypeViewController: AAContentTableController { } } } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewController.swift index 371bc03798..2e1be90ce5 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewController.swift @@ -4,19 +4,19 @@ import UIKit -public class AAGroupViewController: AAContentTableController { +open class AAGroupViewController: AAContentTableController { - private let membersSort = { (left: ACGroupMember, right: ACGroupMember) -> Bool in + fileprivate let membersSort = { (left: ACGroupMember, right: ACGroupMember) -> Bool in let lname = Actor.getUserWithUid(left.uid).getNameModel().get() let rname = Actor.getUserWithUid(right.uid).getNameModel().get() - return lname < rname + return lname! < rname! } - public var headerRow: AAAvatarRow! - public var memberRows: AAManagedArrayRows! + open var headerRow: AAAvatarRow! + open var memberRows: AAManagedArrayRows! public init (gid: Int) { - super.init(style: AAContentTableStyle.SettingsPlain) + super.init(style: AAContentTableStyle.settingsPlain) self.gid = gid self.autoTrack = true @@ -30,11 +30,11 @@ public class AAGroupViewController: AAContentTableController { } - public override func tableDidLoad() { + open override func tableDidLoad() { // NavigationBar if self.group.isCanEditInfo.get().booleanValue() { - self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationEdit"), style: .Plain, target: self, action: #selector(editDidPressed)) + self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationEdit"), style: .plain, target: self, action: #selector(editDidPressed)) } else { self.navigationItem.rightBarButtonItem = nil } @@ -53,13 +53,13 @@ public class AAGroupViewController: AAContentTableController { r.avatarDidTap = { (view) -> () in let avatar = self.group.getAvatarModel().get() - if avatar != nil && avatar.fullImage != nil { + if avatar != nil && avatar?.fullImage != nil { - let full = avatar.fullImage.fileReference - let small = avatar.smallImage.fileReference - let size = CGSize(width: Int(avatar.fullImage.width), height: Int(avatar.fullImage.height)) + let full = avatar?.fullImage.fileReference + let small = avatar?.smallImage.fileReference + let size = CGSize(width: Int((avatar?.fullImage.width)!), height: Int((avatar?.fullImage.height)!)) - self.presentViewController(AAPhotoPreviewController(file: full, previewFile: small, size: size, fromView: view), animated: true, completion: nil) + self.present(AAPhotoPreviewController(file: full!, previewFile: small, size: size, fromView: view), animated: true, completion: nil) } } } @@ -71,7 +71,7 @@ public class AAGroupViewController: AAContentTableController { s.autoSeparatorTopOffset = 1 - s.header(AALocalized("GroupDescription").uppercaseString).height = 22 + s.header(AALocalized("GroupDescription").uppercased()).height = 22 s.text(nil, content: about) } @@ -88,11 +88,11 @@ public class AAGroupViewController: AAContentTableController { if (ActorSDK.sharedActor().enableCalls) { let members = (group.members.get() as! JavaUtilHashSet).size() if (members <= 20) { // Temporary limitation - if group.groupType == ACGroupType.GROUP() { + if group.groupType == ACGroupType.group() { section { (s) -> () in s.action("CallsStartGroupAudio") { (r) -> () in r.selectAction = { () -> Bool in - self.execute(Actor.doCallWithGid(jint(self.gid))) + self.execute(Actor.doCall(withGid: jint(self.gid))) return true } } @@ -107,35 +107,35 @@ public class AAGroupViewController: AAContentTableController { // Notifications s.common { (r) -> () in - let groupPeer: ACPeer! = ACPeer.groupWithInt(jint(self.gid)) + let groupPeer: ACPeer! = ACPeer.group(with: jint(self.gid)) - r.style = .Switch + r.style = .switch r.content = AALocalized("GroupNotifications") r.bindAction = { (r) -> () in - r.switchOn = Actor.isNotificationsEnabledWithPeer(groupPeer) + r.switchOn = Actor.isNotificationsEnabled(with: groupPeer) } r.switchAction = { (on: Bool) -> () in - Actor.changeNotificationsEnabledWithPeer(groupPeer, withValue: on) + Actor.changeNotificationsEnabled(with: groupPeer, withValue: on) } if(ActorSDK.sharedActor().enableChatGroupSound) { - if(Actor.isNotificationsEnabledWithPeer(groupPeer)){ + if(Actor.isNotificationsEnabled(with: groupPeer)){ r.selectAction = {() -> Bool in // Sound: Choose sound let setRingtoneController = AARingtonesViewController() - let soundForGroupe = Actor.getNotificationsSoundWithPeer(groupPeer) - setRingtoneController.selectedRingtone = (soundForGroupe != nil) ? soundForGroupe : "" + let soundForGroupe = Actor.getNotificationsSound(with: groupPeer) + setRingtoneController.selectedRingtone = (soundForGroupe != nil) ? soundForGroupe! : "" setRingtoneController.completion = {(selectedSound:String) in - Actor.changeNotificationsSoundPeer(groupPeer, withValue: selectedSound) + Actor.changeNotificationsSound(groupPeer, withValue: selectedSound) } let navigationController = AANavigationController(rootViewController: setRingtoneController) if (AADevice.isiPad) { - navigationController.modalInPopover = true - navigationController.modalPresentationStyle = UIModalPresentationStyle.CurrentContext + navigationController.isModalInPopover = true + navigationController.modalPresentationStyle = UIModalPresentationStyle.currentContext } - self.presentViewController(navigationController, animated: true, completion: nil) + self.present(navigationController, animated: true, completion: nil) return false } @@ -158,7 +158,7 @@ public class AAGroupViewController: AAContentTableController { if self.group.isCanViewMembers.get().booleanValue() && self.group.isAsyncMembers.get().booleanValue() { s.common({ (r) -> () in r.content = AALocalized("GroupViewMembers") - r.style = .Normal + r.style = .normal r.selectAction = { () -> Bool in self.navigateNext(AAGroupViewMembersController(gid: self.gid)) return false @@ -171,10 +171,10 @@ public class AAGroupViewController: AAContentTableController { let addParticipantController = AAAddParticipantViewController(gid: self.gid) let navigationController = AANavigationController(rootViewController: addParticipantController) if (AADevice.isiPad) { - navigationController.modalInPopover = true - navigationController.modalPresentationStyle = UIModalPresentationStyle.CurrentContext + navigationController.isModalInPopover = true + navigationController.modalPresentationStyle = UIModalPresentationStyle.currentContext } - self.presentViewController(navigationController, animated: true, completion: nil) + self.present(navigationController, animated: true, completion: nil) return false } @@ -194,7 +194,7 @@ public class AAGroupViewController: AAContentTableController { s.headerHeight = 0 // Members: Header - s.header(AALocalized("GroupViewMembers").uppercaseString) + s.header(AALocalized("GroupViewMembers").uppercased()) // Members: Add s.action("GroupAddParticipant") { (r) -> () in @@ -205,10 +205,10 @@ public class AAGroupViewController: AAContentTableController { let addParticipantController = AAAddParticipantViewController(gid: self.gid) let navigationController = AANavigationController(rootViewController: addParticipantController) if (AADevice.isiPad) { - navigationController.modalInPopover = true - navigationController.modalPresentationStyle = UIModalPresentationStyle.CurrentContext + navigationController.isModalInPopover = true + navigationController.modalPresentationStyle = UIModalPresentationStyle.currentContext } - self.presentViewController(navigationController, animated: true, completion: nil) + self.present(navigationController, animated: true, completion: nil) return false } } @@ -216,15 +216,15 @@ public class AAGroupViewController: AAContentTableController { // Members: List self.memberRows = s.arrays { (r: AAManagedArrayRows) -> () in r.height = 48 - r.data = self.group.members.get().toArray().toSwiftArray() - r.data.sortInPlace(self.membersSort) + r.data = (self.group.members.get() as AnyObject).toArray().toSwiftArray() + r.data.sort(by: self.membersSort) r.bindData = { (c, d) -> () in let user = Actor.getUserWithUid(d.uid) c.bind(user, isAdmin: d.isAdministrator) // Notify to request onlines - Actor.onUserVisibleWithUid(d.uid) + Actor.onUserVisible(withUid: d.uid) } r.selectAction = { (d) -> Bool in @@ -246,27 +246,27 @@ public class AAGroupViewController: AAContentTableController { } a.action("GroupMemberWrite") { () -> () in - if let customController = ActorSDK.sharedActor().delegate.actorControllerForConversation(ACPeer.userWithInt(user.getId())) { + if let customController = ActorSDK.sharedActor().delegate.actorControllerForConversation(ACPeer.user(with: user.getId())) { self.navigateDetail(customController) } else { - self.navigateDetail(ConversationViewController(peer: ACPeer.userWithInt(user.getId()))) + self.navigateDetail(ConversationViewController(peer: ACPeer.user(with: user.getId()))) } - self.popover?.dismissPopoverAnimated(true) + self.popover?.dismiss(animated: true) } a.action("GroupMemberCall", closure: { () -> () in let phones = user.getPhonesModel().get() - if phones.size() == 0 { + if phones?.size() == 0 { self.alertUser("GroupMemberCallNoPhones") - } else if phones.size() == 1 { - let number = phones.getWithInt(0) - ActorSDK.sharedActor().openUrl("telprompt://+\(number.phone)") + } else if phones?.size() == 1 { + let number = phones!.getWith(0) + ActorSDK.sharedActor().openUrl("telprompt://+\(number!.phone)") } else { var numbers = [String]() - for i in 0.. () in if (index >= 0) { - let number = phones.getWithInt(jint(index)) - ActorSDK.sharedActor().openUrl("telprompt://+\(number.phone)") + let number = phones!.getWith(jint(index)) + ActorSDK.sharedActor().openUrl("telprompt://+\(number!.phone)") } }) } @@ -291,8 +291,8 @@ public class AAGroupViewController: AAContentTableController { let name = Actor.getUserWithUid(d.uid).getNameModel().get() a.destructive("GroupMemberKick") { () -> () in self.confirmDestructive(AALocalized("GroupMemberKickMessage") - .replace("{name}", dest: name), action: AALocalized("GroupMemberKickAction")) { - self.executeSafe(Actor.kickMemberCommandWithGid(jint(self.gid), withUid: user.getId())) + .replace("{name}", dest: name!), action: AALocalized("GroupMemberKickAction")) { + self.executeSafe(Actor.kickMemberCommand(withGid: jint(self.gid), withUid: user.getId())) } } } @@ -309,18 +309,18 @@ public class AAGroupViewController: AAContentTableController { section { (s) -> () in s.common({ (r) -> () in - if self.group.groupType == ACGroupType.CHANNEL() { + if self.group.groupType == ACGroupType.channel() { r.content = AALocalized("ActionLeaveChannel") } else { r.content = AALocalized("ActionDeleteAndExit") } - r.style = .Destructive + r.style = .destructive r.selectAction = { () -> Bool in let title: String let action: String - if self.group.groupType == ACGroupType.CHANNEL() { + if self.group.groupType == ACGroupType.channel() { title = AALocalized("ActionLeaveChannelMessage") action = AALocalized("ActionLeaveChannelAction") } else { @@ -328,7 +328,7 @@ public class AAGroupViewController: AAContentTableController { action = AALocalized("ActionDeleteAndExitMessageAction") } self.confirmDestructive(title, action: action, yes: { () -> () in - self.executePromise(Actor.leaveAndDeleteGroupWithGid(jint(self.gid))) + self.executePromise(Actor.leaveAndDeleteGroup(withGid: jint(self.gid))) }) return true @@ -338,7 +338,7 @@ public class AAGroupViewController: AAContentTableController { } } - public override func tableWillBind(binder: AABinder) { + open override func tableWillBind(_ binder: AABinder) { // Bind group info @@ -355,7 +355,7 @@ public class AAGroupViewController: AAContentTableController { binder.bind(group.getMembersModel()) { (value: JavaUtilHashSet?) -> () in if let v = value { self.memberRows.data = v.toArray().toSwiftArray() - self.memberRows.data.sortInPlace(self.membersSort) + self.memberRows.data.sort(by: self.membersSort) self.memberRows.reload() } } @@ -379,7 +379,7 @@ public class AAGroupViewController: AAContentTableController { } } - public func editDidPressed() { + open func editDidPressed() { self.presentInNavigation(AAGroupEditInfoController(gid: gid)) } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewMembersController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewMembersController.swift index 523ba3afaa..a2499c9217 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewMembersController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupViewMembersController.swift @@ -4,22 +4,22 @@ import Foundation -public class AAGroupViewMembersController: AAContentTableController { +open class AAGroupViewMembersController: AAContentTableController { - private var membersRow: AAManagedArrayRows! + fileprivate var membersRow: AAManagedArrayRows! - private var isLoaded = false - private var isLoading = false - private var next: IOSByteArray! = nil + fileprivate var isLoaded = false + fileprivate var isLoading = false + open var nextBatch: IOSByteArray! = nil public init(gid: Int) { - super.init(style: .Plain) + super.init(style: .plain) self.gid = gid navigationItem.title = AALocalized("GroupViewMembers") if group.isCanInviteMembers.get().booleanValue() { - navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.Add, target: self, action: #selector(didAddPressed)) + navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.add, target: self, action: #selector(didAddPressed)) } } @@ -27,7 +27,7 @@ public class AAGroupViewMembersController: AAContentTableController { fatalError("init(coder:) has not been implemented") } - public override func tableDidLoad() { + open override func tableDidLoad() { section { (s) in self.membersRow = s.arrays({ (r: AAManagedArrayRows) -> () in r.height = 48 @@ -38,7 +38,7 @@ public class AAGroupViewMembersController: AAContentTableController { c.bind(user, isAdmin: d.isAdministrator) // Notify to request onlines - Actor.onUserVisibleWithUid(d.uid) + Actor.onUserVisible(withUid: d.uid) } r.itemShown = { (index, d) in @@ -66,26 +66,26 @@ public class AAGroupViewMembersController: AAContentTableController { } a.action("GroupMemberWrite") { () -> () in - if let customController = ActorSDK.sharedActor().delegate.actorControllerForConversation(ACPeer.userWithInt(user.getId())) { + if let customController = ActorSDK.sharedActor().delegate.actorControllerForConversation(ACPeer.user(with: user.getId())) { self.navigateDetail(customController) } else { - self.navigateDetail(ConversationViewController(peer: ACPeer.userWithInt(user.getId()))) + self.navigateDetail(ConversationViewController(peer: ACPeer.user(with: user.getId()))) } - self.popover?.dismissPopoverAnimated(true) + self.popover?.dismiss(animated: true) } a.action("GroupMemberCall", closure: { () -> () in - let phones = user.getPhonesModel().get() + let phones = user.getPhonesModel().get()! if phones.size() == 0 { self.alertUser("GroupMemberCallNoPhones") } else if phones.size() == 1 { - let number = phones.getWithInt(0) + let number = phones.getWith(0)! ActorSDK.sharedActor().openUrl("telprompt://+\(number.phone)") } else { var numbers = [String]() for i in 0.. () in if (index >= 0) { - let number = phones.getWithInt(jint(index)) + let number = phones.getWith(jint(index))! ActorSDK.sharedActor().openUrl("telprompt://+\(number.phone)") } }) @@ -111,8 +111,8 @@ public class AAGroupViewMembersController: AAContentTableController { let name = Actor.getUserWithUid(d.uid).getNameModel().get() a.destructive("GroupMemberKick") { () -> () in self.confirmDestructive(AALocalized("GroupMemberKickMessage") - .replace("{name}", dest: name), action: AALocalized("GroupMemberKickAction")) { - self.executeSafe(Actor.kickMemberCommandWithGid(jint(self.gid), withUid: user.getId())) + .replace("{name}", dest: name!), action: AALocalized("GroupMemberKickAction")) { + self.executeSafe(Actor.kickMemberCommand(withGid: jint(self.gid), withUid: user.getId())) } } } @@ -126,7 +126,7 @@ public class AAGroupViewMembersController: AAContentTableController { loadMore() } - private func loadMore() { + fileprivate func loadMore() { if isLoading { return } @@ -135,13 +135,13 @@ public class AAGroupViewMembersController: AAContentTableController { } isLoading = true - Actor.loadMembersWithGid(jint(gid), withLimit: 20, withNext: next).then { (slice: ACGroupMembersSlice!) in + Actor.loadMembers(withGid: jint(gid), withLimit: 20, withNext: nextBatch).then { (slice: ACGroupMembersSlice!) in for i in 0.. () in s.headerText = AALocalized("GroupInviteLinkTitle") @@ -44,7 +44,7 @@ public class AAInviteLinkViewController: AAContentTableController { section { (s) -> () in s.action("ActionCopyLink") { (r) -> () in r.selectAction = { () -> Bool in - UIPasteboard.generalPasteboard().string = self.currentUrl + UIPasteboard.general.string = self.currentUrl self.alertUser("AlertLinkCopied") return true } @@ -52,9 +52,9 @@ public class AAInviteLinkViewController: AAContentTableController { s.action("ActionShareLink") { (r) -> () in r.selectAction = { () -> Bool in var sharingItems = [AnyObject]() - sharingItems.append(self.currentUrl!) + sharingItems.append(self.currentUrl! as AnyObject) let activityViewController = UIActivityViewController(activityItems: sharingItems, applicationActivities: nil) - self.presentViewController(activityViewController, animated: true, completion: nil) + self.present(activityViewController, animated: true, completion: nil) return true } } @@ -71,18 +71,18 @@ public class AAInviteLinkViewController: AAContentTableController { } } - executeSafe(Actor.requestInviteLinkCommandWithGid(jint(gid))) { (val) -> Void in + executeSafe(Actor.requestInviteLinkCommand(withGid: jint(gid))) { (val) -> Void in self.currentUrl = val as? String self.urlRow.reload() - self.tableView.hidden = false + self.tableView.isHidden = false } } - public func reloadLink() { - executeSafe(Actor.requestRevokeLinkCommandWithGid(jint(gid))) { (val) -> Void in + open func reloadLink() { + executeSafe(Actor.requestRevokeLinkCommand(withGid: jint(gid))) { (val) -> Void in self.currentUrl = val as? String self.urlRow.reload() - self.tableView.hidden = false + self.tableView.isHidden = false } } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/Cells/AAGroupMemberCell.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/Cells/AAGroupMemberCell.swift index 5ff50f7042..3b47e0c6cc 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/Cells/AAGroupMemberCell.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/Cells/AAGroupMemberCell.swift @@ -4,18 +4,18 @@ import UIKit -public class AAGroupMemberCell: AATableViewCell { +open class AAGroupMemberCell: AATableViewCell { // Views - public var nameLabel = UILabel() - public var onlineLabel = UILabel() - public var avatarView = AAAvatarView() - public var adminLabel = UILabel() + open var nameLabel = UILabel() + open var onlineLabel = UILabel() + open var avatarView = AAAvatarView() + open var adminLabel = UILabel() // Binder - public var binder = AABinder() + open var binder = AABinder() // Contstructors @@ -27,14 +27,14 @@ public class AAGroupMemberCell: AATableViewCell { contentView.addSubview(avatarView) - nameLabel.font = UIFont.systemFontOfSize(18.0) + nameLabel.font = UIFont.systemFont(ofSize: 18.0) nameLabel.textColor = appStyle.cellTextColor contentView.addSubview(nameLabel) - onlineLabel.font = UIFont.systemFontOfSize(14.0) + onlineLabel.font = UIFont.systemFont(ofSize: 14.0) contentView.addSubview(onlineLabel) - adminLabel.font = UIFont.systemFontOfSize(14.0) + adminLabel.font = UIFont.systemFont(ofSize: 14.0) adminLabel.textColor = appStyle.cellDestructiveColor contentView.addSubview(adminLabel) } @@ -45,19 +45,19 @@ public class AAGroupMemberCell: AATableViewCell { // Binding - public func setUsername(username: String) { + open func setUsername(_ username: String) { nameLabel.text = username } - public func bind(user: ACUserVM, isAdmin: Bool) { + open func bind(_ user: ACUserVM, isAdmin: Bool) { // Bind name and avatar - let name = user.getNameModel().get() + let name = user.getNameModel().get()! nameLabel.text = name avatarView.bind(name, id: Int(user.getId()), avatar: user.getAvatarModel().get()) // Bind admin flag - adminLabel.hidden = !isAdmin + adminLabel.isHidden = !isAdmin // Bind onlines @@ -70,8 +70,8 @@ public class AAGroupMemberCell: AATableViewCell { if value != nil { self.onlineLabel.showView() - self.onlineLabel.text = Actor.getFormatter().formatPresence(value!, withSex: user.getSex()) - if value!.state.ordinal() == ACUserPresence_State.ONLINE().ordinal() { + self.onlineLabel.text = Actor.getFormatter().formatPresence(value!, with: user.getSex()) + if value!.state.ordinal() == ACUserPresence_State.online().ordinal() { self.onlineLabel.textColor = self.appStyle.userOnlineColor } else { self.onlineLabel.textColor = self.appStyle.userOfflineColor @@ -84,14 +84,14 @@ public class AAGroupMemberCell: AATableViewCell { } } - public override func prepareForReuse() { + open override func prepareForReuse() { super.prepareForReuse() binder.unbindAll() } // Layouting - public override func layoutSubviews() { + open override func layoutSubviews() { super.layoutSubviews() let userAvatarViewFrameSize: CGFloat = CGFloat(44) @@ -99,7 +99,7 @@ public class AAGroupMemberCell: AATableViewCell { var w: CGFloat = contentView.bounds.size.width - 65.0 - 8.0 - if !adminLabel.hidden { + if !adminLabel.isHidden { adminLabel.frame = CGRect(x: contentView.width - adminLabel.width - 8, y: 5, width: adminLabel.width, height: 42) w -= adminLabel.width + 8 } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Location/AALocationPickerController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Location/AALocationPickerController.swift index 4e402d339f..e35f5260ce 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Location/AALocationPickerController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Location/AALocationPickerController.swift @@ -5,20 +5,20 @@ import Foundation import MapKit -public class AALocationPickerController: AAViewController, CLLocationManagerDelegate, MKMapViewDelegate { +open class AALocationPickerController: AAViewController, CLLocationManagerDelegate, MKMapViewDelegate { - public var delegate: AALocationPickerControllerDelegate? = nil + open var delegate: AALocationPickerControllerDelegate? = nil - private let locationManager = CLLocationManager() - private let map = MKMapView() - private let pinPoint = AAMapPinPointView() - private let locationPinOffset = CGPointMake(0.0, 33.0) + fileprivate let locationManager = CLLocationManager() + fileprivate let map = MKMapView() + fileprivate let pinPoint = AAMapPinPointView() + fileprivate let locationPinOffset = CGPoint(x: 0.0, y: 33.0) override init() { super.init() navigationItem.title = AALocalized("LocationTitle") - navigationItem.leftBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationCancel"), style: .Plain, target: self, action: #selector(AALocationPickerController.cancellDidTap)) + navigationItem.leftBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationCancel"), style: .plain, target: self, action: #selector(AALocationPickerController.cancellDidTap)) updateAuthStatus(CLLocationManager.authorizationStatus()) @@ -46,11 +46,11 @@ public class AALocationPickerController: AAViewController, CLLocationManagerDele // // } - public func mapView(mapView: MKMapView, regionWillChangeAnimated animated: Bool) { + open func mapView(_ mapView: MKMapView, regionWillChangeAnimated animated: Bool) { pinPoint.risePin(true, animated: true) } - public func mapView(mapView: MKMapView, regionDidChangeAnimated animated: Bool) { + open func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) { pinPoint.risePin(false, animated: true) } @@ -62,40 +62,40 @@ public class AALocationPickerController: AAViewController, CLLocationManagerDele delegate?.locationPickerDidPicked(self, latitude: map.centerCoordinate.latitude, longitude: map.centerCoordinate.longitude) } - func updateAuthStatus(status: CLAuthorizationStatus) { - if (status == CLAuthorizationStatus.Denied) { + func updateAuthStatus(_ status: CLAuthorizationStatus) { + if (status == CLAuthorizationStatus.denied) { // User explictly denied access to maps showPlaceholderWithImage(UIImage.bundled("location_placeholder"), title: AALocalized("Placeholder_Location_Title"), subtitle: AALocalized("Placeholder_Location_Message")) - map.hidden = true - pinPoint.hidden = true + map.isHidden = true + pinPoint.isHidden = true navigationItem.rightBarButtonItem = nil - } else if (status == CLAuthorizationStatus.Restricted || status == CLAuthorizationStatus.NotDetermined) { + } else if (status == CLAuthorizationStatus.restricted || status == CLAuthorizationStatus.notDetermined) { // App doesn't complete auth request - map.hidden = false - pinPoint.hidden = false - navigationItem.rightBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationDone"), style: .Done, target: self, action: #selector(AALocationPickerController.doneDidTap)) + map.isHidden = false + pinPoint.isHidden = false + navigationItem.rightBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationDone"), style: .done, target: self, action: #selector(AALocationPickerController.doneDidTap)) hidePlaceholder() } else { // Authorised - map.hidden = false - pinPoint.hidden = false - navigationItem.rightBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationDone"), style: .Done, target: self, action: #selector(AALocationPickerController.doneDidTap)) + map.isHidden = false + pinPoint.isHidden = false + navigationItem.rightBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationDone"), style: .done, target: self, action: #selector(AALocationPickerController.doneDidTap)) hidePlaceholder() } } - override public func viewDidLayoutSubviews() { + override open func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() map.frame = self.view.bounds pinPoint.centerIn(self.view.bounds) } - public func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) { + open func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) { updateAuthStatus(status) } - public func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { + open func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { let location = locations.first! map.setRegion(MKCoordinateRegion(center: location.coordinate, span: MKCoordinateSpanMake(0.05, 0.05)), animated: true) locationManager.stopUpdatingLocation() @@ -103,6 +103,6 @@ public class AALocationPickerController: AAViewController, CLLocationManagerDele } public protocol AALocationPickerControllerDelegate { - func locationPickerDidPicked(controller: AALocationPickerController, latitude: Double, longitude: Double) - func locationPickerDidCancelled(controller: AALocationPickerController) -} \ No newline at end of file + func locationPickerDidPicked(_ controller: AALocationPickerController, latitude: Double, longitude: Double) + func locationPickerDidCancelled(_ controller: AALocationPickerController) +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/AACollectionViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/AACollectionViewController.swift index 6ff394ffa3..40a01c6d22 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/AACollectionViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/AACollectionViewController.swift @@ -4,41 +4,41 @@ import Foundation -public class AACollectionViewController: AAViewController, UICollectionViewDelegate, UICollectionViewDataSource { +open class AACollectionViewController: AAViewController, UICollectionViewDelegate, UICollectionViewDataSource { - public var collectionView:UICollectionView! + open var collectionView:UICollectionView! public init(collectionLayout: UICollectionViewLayout) { super.init() - collectionView = UICollectionView(frame: CGRectZero, collectionViewLayout: collectionLayout) + collectionView = UICollectionView(frame: CGRect.zero, collectionViewLayout: collectionLayout) } public required init(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - public override func loadView() { + open override func loadView() { super.loadView() collectionView.delegate = self collectionView.dataSource = self view.addSubview(collectionView) - navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: UIBarButtonItemStyle.Plain, target: nil, action: nil) + navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: UIBarButtonItemStyle.plain, target: nil, action: nil) } - public override func viewWillLayoutSubviews() { + open override func viewWillLayoutSubviews() { super.viewWillLayoutSubviews() collectionView.frame = view.bounds; } - public func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + open func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { fatalError("Not implemented!") } - public func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell { + open func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { fatalError("Not implemented!") } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/AAContentTableController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/AAContentTableController.swift index 8ec10f42dd..38d330da78 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/AAContentTableController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/AAContentTableController.swift @@ -6,16 +6,16 @@ import Foundation public enum AAContentTableStyle { - case SettingsPlain - case SettingsGrouped - case Plain + case settingsPlain + case settingsGrouped + case plain } -public class AAContentTableController: AAManagedTableController, AAManagedTableControllerDelegate { +open class AAContentTableController: AAManagedTableController, AAManagedTableControllerDelegate { - private var isInLoad: Bool = false + fileprivate var isInLoad: Bool = false - public var autoSections = true + open var autoSections = true // Controller constructor @@ -24,7 +24,7 @@ public class AAContentTableController: AAManagedTableController, AAManagedTableC self.managedTableDelegate = self - self.autoSections = style != .Plain + self.autoSections = style != .plain } public required init(coder aDecoder: NSCoder) { @@ -33,7 +33,7 @@ public class AAContentTableController: AAManagedTableController, AAManagedTableC // DSL Implementation - public func section(@noescape closure: (s: AAManagedSection) -> ()) -> AAManagedSection { + open func section(_ closure: (_ s: AAManagedSection) -> ()) -> AAManagedSection { if !isInLoad { fatalError("Unable to change sections not during tableDidLoad method call") } @@ -47,15 +47,15 @@ public class AAContentTableController: AAManagedTableController, AAManagedTableC s.headerHeight = 0 } } - closure(s: s) + closure(s) return s } - public func search(cell: C.Type, @noescape closure: (s: AAManagedSearchConfig) -> ()) { + open func search(_ cell: C.Type, closure: (_ s: AAManagedSearchConfig) -> ()) where C: AABindedSearchCell, C: UITableViewCell { managedTable.search(cell, closure: closure) } - public func afterTableCreated() { + open func afterTableCreated() { if autoSections { managedTable.sections.last?.footerHeight = 30 } @@ -63,25 +63,25 @@ public class AAContentTableController: AAManagedTableController, AAManagedTableC // Implement it in subclass - public func tableWillLoad() { + open func tableWillLoad() { } - public func tableDidLoad() { + open func tableDidLoad() { } - public func tableWillBind(binder: AABinder) { + open func tableWillBind(_ binder: AABinder) { } - public func tableWillUnbind(binder: AABinder) { + open func tableWillUnbind(_ binder: AABinder) { } // Delegate implementation - public func managedTableLoad(controller: AAManagedTableController, table: AAManagedTable) { + open func managedTableLoad(_ controller: AAManagedTableController, table: AAManagedTable) { isInLoad = true table.beginUpdates() tableDidLoad() @@ -90,15 +90,15 @@ public class AAContentTableController: AAManagedTableController, AAManagedTableC isInLoad = false } - public func managedTableBind(controller: AAManagedTableController, table: AAManagedTable, binder: AABinder) { + open func managedTableBind(_ controller: AAManagedTableController, table: AAManagedTable, binder: AABinder) { tableWillBind(binder) } - public func managedTableUnbind(controller: AAManagedTableController, table: AAManagedTable, binder: AABinder) { + open func managedTableUnbind(_ controller: AAManagedTableController, table: AAManagedTable, binder: AABinder) { tableWillUnbind(binder) } - public func managedTableWillLoad(controller: AAManagedTableController) { + open func managedTableWillLoad(_ controller: AAManagedTableController) { tableWillLoad() } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/AAImagePickerController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/AAImagePickerController.swift index 3c7cdc2479..fce7d62e6b 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/AAImagePickerController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/AAImagePickerController.swift @@ -4,8 +4,8 @@ import Foundation -public class AAImagePickerController: UIImagePickerController { - public override func preferredStatusBarStyle() -> UIStatusBarStyle { +open class AAImagePickerController: UIImagePickerController { + open override var preferredStatusBarStyle : UIStatusBarStyle { return ActorSDK.sharedActor().style.vcStatusBarStyle } @@ -26,4 +26,4 @@ public class AAImagePickerController: UIImagePickerController { // super.viewWillDisappear(animated) // UIApplication.sharedApplication().setStatusBarHidden(false, withAnimation: UIStatusBarAnimation.Fade) // } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/AAManagedRange.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/AAManagedRange.swift index 0f5c9c6681..813293a902 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/AAManagedRange.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/AAManagedRange.swift @@ -10,95 +10,95 @@ public protocol AAManagedRange { // Initing Table - func initTable(table: AAManagedTable) + func initTable(_ table: AAManagedTable) // Total items count - func rangeNumberOfItems(table: AAManagedTable) -> Int + func rangeNumberOfItems(_ table: AAManagedTable) -> Int // Cell - func rangeCellHeightForItem(table: AAManagedTable, indexPath: AARangeIndexPath) -> CGFloat + func rangeCellHeightForItem(_ table: AAManagedTable, indexPath: AARangeIndexPath) -> CGFloat - func rangeCellForItem(table: AAManagedTable, indexPath: AARangeIndexPath) -> UITableViewCell + func rangeCellForItem(_ table: AAManagedTable, indexPath: AARangeIndexPath) -> UITableViewCell // Selection - func rangeCanSelect(table: AAManagedTable, indexPath: AARangeIndexPath) -> Bool + func rangeCanSelect(_ table: AAManagedTable, indexPath: AARangeIndexPath) -> Bool - func rangeSelect(table: AAManagedTable, indexPath: AARangeIndexPath) -> Bool + func rangeSelect(_ table: AAManagedTable, indexPath: AARangeIndexPath) -> Bool // Copying - func rangeCanCopy(table: AAManagedTable, indexPath: AARangeIndexPath) -> Bool + func rangeCanCopy(_ table: AAManagedTable, indexPath: AARangeIndexPath) -> Bool - func rangeCopy(table: AAManagedTable, indexPath: AARangeIndexPath) + func rangeCopy(_ table: AAManagedTable, indexPath: AARangeIndexPath) // Delete - func rangeCanDelete(table: AAManagedTable, indexPath: AARangeIndexPath) -> Bool + func rangeCanDelete(_ table: AAManagedTable, indexPath: AARangeIndexPath) -> Bool - func rangeDelete(table: AAManagedTable, indexPath: AARangeIndexPath) + func rangeDelete(_ table: AAManagedTable, indexPath: AARangeIndexPath) // Binding - func rangeBind(table: AAManagedTable, binder: AABinder) + func rangeBind(_ table: AAManagedTable, binder: AABinder) - func rangeUnbind(table: AAManagedTable, binder: AABinder) + func rangeUnbind(_ table: AAManagedTable, binder: AABinder) } // Default implementations of ACManagedRangeDelegate public extension AAManagedRange { - public func rangeCanSelect(table: AAManagedTable, indexPath: AARangeIndexPath) -> Bool { + public func rangeCanSelect(_ table: AAManagedTable, indexPath: AARangeIndexPath) -> Bool { // Do nothing return false } - public func rangeSelect(table: AAManagedTable, indexPath: AARangeIndexPath) -> Bool { + public func rangeSelect(_ table: AAManagedTable, indexPath: AARangeIndexPath) -> Bool { // Do nothing return false } - public func rangeCanCopy(table: AAManagedTable, indexPath: AARangeIndexPath) -> Bool { + public func rangeCanCopy(_ table: AAManagedTable, indexPath: AARangeIndexPath) -> Bool { // Do nothing return false } - public func rangeCopy(table: AAManagedTable, indexPath: AARangeIndexPath) { + public func rangeCopy(_ table: AAManagedTable, indexPath: AARangeIndexPath) { // Do nothing } - public func rangeCanDelete(table: AAManagedTable, indexPath: AARangeIndexPath) -> Bool { + public func rangeCanDelete(_ table: AAManagedTable, indexPath: AARangeIndexPath) -> Bool { // Do nothing return false } - public func rangeDelete(table: AAManagedTable, indexPath: AARangeIndexPath) { + public func rangeDelete(_ table: AAManagedTable, indexPath: AARangeIndexPath) { // Do nothing } - public func rangeBind(table: AAManagedTable, binder: AABinder) { + public func rangeBind(_ table: AAManagedTable, binder: AABinder) { // Do nothing } - public func rangeUnbind(table: AAManagedTable, binder: AABinder) { + public func rangeUnbind(_ table: AAManagedTable, binder: AABinder) { // Do nothing } } -public class AARangeIndexPath { +open class AARangeIndexPath { - public let section: Int - public let range: Int - public let item: Int - public let indexPath: NSIndexPath + open let section: Int + open let range: Int + open let item: Int + open let indexPath: IndexPath - public init(section: Int, range: Int, item: Int, indexPath: NSIndexPath) { + public init(section: Int, range: Int, item: Int, indexPath: IndexPath) { self.section = section self.range = range self.item = item self.indexPath = indexPath } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/AAManagedSection.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/AAManagedSection.swift index d8eafc7c4e..b45fd5bc20 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/AAManagedSection.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/AAManagedSection.swift @@ -4,24 +4,24 @@ import Foundation -public class AAManagedSection { +open class AAManagedSection { - public var headerHeight: Double = 0 - public var footerHeight: Double = 0 + open var headerHeight: Double = 0 + open var footerHeight: Double = 0 - public var headerText: String? = nil - public var footerText: String? = nil + open var headerText: String? = nil + open var footerText: String? = nil - public let index: Int + open let index: Int - public var autoSeparatorTopOffset: Int = 0 - public var autoSeparatorBottomOffset: Int = 0 - public var autoSeparators: Bool = false - public var autoSeparatorsInset: CGFloat = 15.0 + open var autoSeparatorTopOffset: Int = 0 + open var autoSeparatorBottomOffset: Int = 0 + open var autoSeparators: Bool = false + open var autoSeparatorsInset: CGFloat = 15.0 - public var regions: [AAManagedRange] = [AAManagedRange]() + open var regions: [AAManagedRange] = [AAManagedRange]() - public unowned let table: AAManagedTable + open unowned let table: AAManagedTable public init(table: AAManagedTable, index: Int) { self.index = index @@ -30,7 +30,7 @@ public class AAManagedSection { // Items count - public func numberOfItems(managedTable: AAManagedTable) -> Int { + open func numberOfItems(_ managedTable: AAManagedTable) -> Int { var res = 0 for r in regions { res += r.rangeNumberOfItems(managedTable) @@ -40,12 +40,12 @@ public class AAManagedSection { // Cells - public func cellHeightForItem(managedTable: AAManagedTable, indexPath: NSIndexPath) -> CGFloat { + open func cellHeightForItem(_ managedTable: AAManagedTable, indexPath: IndexPath) -> CGFloat { let r = findCell(managedTable, indexPath: indexPath) return r.cells.rangeCellHeightForItem(managedTable, indexPath: r.index) } - public func cellForItem(managedTable: AAManagedTable, indexPath: NSIndexPath) -> UITableViewCell { + open func cellForItem(_ managedTable: AAManagedTable, indexPath: IndexPath) -> UITableViewCell { let r = findCell(managedTable, indexPath: indexPath) let res = r.cells.rangeCellForItem(managedTable, indexPath: r.index) @@ -54,7 +54,7 @@ public class AAManagedSection { // Top separator - if managedTable.style == .Plain { + if managedTable.style == .plain { // Don't show for plain style cell.topSeparatorVisible = false @@ -62,21 +62,21 @@ public class AAManagedSection { // Showing only for top cell cell.topSeparatorLeftInset = 0 - cell.topSeparatorVisible = indexPath.row == autoSeparatorTopOffset + cell.topSeparatorVisible = (indexPath as NSIndexPath).row == autoSeparatorTopOffset } // Bottom separator - if indexPath.row >= autoSeparatorTopOffset { + if (indexPath as NSIndexPath).row >= autoSeparatorTopOffset { cell.bottomSeparatorVisible = true - if managedTable.style == .Plain { + if managedTable.style == .plain { // Don't make last full line separator cell.bottomSeparatorLeftInset = autoSeparatorsInset } else { - if indexPath.row == numberOfItems(managedTable) - 1 { + if (indexPath as NSIndexPath).row == numberOfItems(managedTable) - 1 { cell.bottomSeparatorLeftInset = 0 } else { cell.bottomSeparatorLeftInset = autoSeparatorsInset @@ -95,36 +95,36 @@ public class AAManagedSection { // Selection - func canSelect(managedTable: AAManagedTable, indexPath: NSIndexPath) -> Bool { + func canSelect(_ managedTable: AAManagedTable, indexPath: IndexPath) -> Bool { let r = findCell(managedTable, indexPath: indexPath) return r.cells.rangeCanSelect(managedTable, indexPath: r.index) } - func select(managedTable: AAManagedTable, indexPath: NSIndexPath) -> Bool { + func select(_ managedTable: AAManagedTable, indexPath: IndexPath) -> Bool { let r = findCell(managedTable, indexPath: indexPath) return r.cells.rangeSelect(managedTable, indexPath: r.index) } // Copying - func canCopy(managedTable: AAManagedTable, indexPath: NSIndexPath) -> Bool { + func canCopy(_ managedTable: AAManagedTable, indexPath: IndexPath) -> Bool { let r = findCell(managedTable, indexPath: indexPath) return r.cells.rangeCanCopy(managedTable, indexPath: r.index) } - func copy(managedTable: AAManagedTable, indexPath: NSIndexPath) { + func copy(_ managedTable: AAManagedTable, indexPath: IndexPath) { let r = findCell(managedTable, indexPath: indexPath) r.cells.rangeCopy(managedTable, indexPath: r.index) } // Deletion - func canDelete(managedTable: AAManagedTable, indexPath: NSIndexPath) -> Bool { + func canDelete(_ managedTable: AAManagedTable, indexPath: IndexPath) -> Bool { let r = findCell(managedTable, indexPath: indexPath) return r.cells.rangeCanDelete(managedTable, indexPath: r.index) } - func delete(managedTable: AAManagedTable, indexPath: NSIndexPath) { + func delete(_ managedTable: AAManagedTable, indexPath: IndexPath) { let r = findCell(managedTable, indexPath: indexPath) r.cells.rangeDelete(managedTable, indexPath: r.index) } @@ -132,13 +132,13 @@ public class AAManagedSection { // Binding - func bind(managedTable: AAManagedTable, binder: AABinder) { + func bind(_ managedTable: AAManagedTable, binder: AABinder) { for s in regions { s.rangeBind(managedTable, binder: binder) } } - func unbind(managedTable: AAManagedTable, binder: AABinder) { + func unbind(_ managedTable: AAManagedTable, binder: AABinder) { for s in regions { s.rangeUnbind(managedTable, binder: binder) } @@ -146,20 +146,20 @@ public class AAManagedSection { // Private Tools - private func findCell(managedTable: AAManagedTable, indexPath: NSIndexPath) -> CellSearchResult { + fileprivate func findCell(_ managedTable: AAManagedTable, indexPath: IndexPath) -> CellSearchResult { var prevLength = 0 for i in 0.. AAManagedSection { + public func setSeparatorsTopOffset(_ offset: Int) -> AAManagedSection { self.autoSeparatorTopOffset = offset return self } - public func setFooterText(footerText: String) -> AAManagedSection { + public func setFooterText(_ footerText: String) -> AAManagedSection { self.footerText = footerText return self } - public func setHeaderText(headerText: String) -> AAManagedSection { + public func setHeaderText(_ headerText: String) -> AAManagedSection { self.headerText = headerText return self } - public func setFooterHeight(footerHeight: Double) -> AAManagedSection { + public func setFooterHeight(_ footerHeight: Double) -> AAManagedSection { self.footerHeight = footerHeight return self } - public func setHeaderHeight(headerHeight: Double) -> AAManagedSection { + public func setHeaderHeight(_ headerHeight: Double) -> AAManagedSection { self.headerHeight = headerHeight return self } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/AAManagedTable.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/AAManagedTable.swift index 5668969e39..4d31247c92 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/AAManagedTable.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/AAManagedTable.swift @@ -4,66 +4,66 @@ import Foundation -public class AAManagedTable { +open class AAManagedTable { //------------------------------------------------------------------------// // Controller of table - public let controller: UIViewController + open let controller: UIViewController // Table view - public let style: AAContentTableStyle - public let tableView: UITableView - public var tableViewDelegate: UITableViewDelegate { get { return baseDelegate } } - public var tableViewDataSource: UITableViewDataSource { get { return baseDelegate } } + open let style: AAContentTableStyle + open let tableView: UITableView + open var tableViewDelegate: UITableViewDelegate { get { return baseDelegate } } + open var tableViewDataSource: UITableViewDataSource { get { return baseDelegate } } // Scrolling closure - public var tableScrollClosure: ((tableView: UITableView) -> ())? + open var tableScrollClosure: ((_ tableView: UITableView) -> ())? // Is fade in/out animated - public var fadeShowing = false + open var fadeShowing = false // Sections of table - public var sections: [AAManagedSection] = [AAManagedSection]() + open var sections: [AAManagedSection] = [AAManagedSection]() // Fixed Height - public var fixedHeight: CGFloat? + open var fixedHeight: CGFloat? // Can Edit All rows - public var canEditAll: Bool? + open var canEditAll: Bool? // Can Delete All rows - public var canDeleteAll: Bool? + open var canDeleteAll: Bool? // Is Table in editing mode - public var isEditing: Bool { + open var isEditing: Bool { get { - return tableView.editing + return tableView.isEditing } } // Is updating sections - private var isUpdating = false + fileprivate var isUpdating = false // Reference to table view delegate/data source - private var baseDelegate: AMBaseTableDelegate! + fileprivate var baseDelegate: AMBaseTableDelegate! // Search - private var isSearchInited: Bool = false - private var isSearchAutoHide: Bool = false - private var searchDisplayController: UISearchDisplayController! - private var searchManagedController: AnyObject! + fileprivate var isSearchInited: Bool = false + fileprivate var isSearchAutoHide: Bool = false + fileprivate var searchDisplayController: UISearchDisplayController! + fileprivate var searchManagedController: AnyObject! //------------------------------------------------------------------------// @@ -72,7 +72,7 @@ public class AAManagedTable { self.controller = controller self.tableView = tableView - if style == .SettingsGrouped { + if style == .settingsGrouped { self.baseDelegate = AMGrouppedTableDelegate(data: self) } else { self.baseDelegate = AMPlainTableDelegate(data: self) @@ -86,14 +86,14 @@ public class AAManagedTable { // Entry point to adding - public func beginUpdates() { + open func beginUpdates() { if isUpdating { fatalError("Already updating table") } isUpdating = true } - public func addSection(autoSeparator: Bool = false) -> AAManagedSection { + open func addSection(_ autoSeparator: Bool = false) -> AAManagedSection { if !isUpdating { fatalError("Table is not in updating mode") } @@ -104,7 +104,7 @@ public class AAManagedTable { return res } - public func search(cell: C.Type, @noescape closure: (s: AAManagedSearchConfig) -> ()) { + open func search(_ cell: C.Type, closure: (_ s: AAManagedSearchConfig) -> ()) where C: AABindedSearchCell, C: UITableViewCell { if !isUpdating { fatalError("Table is not in updating mode") @@ -120,7 +120,7 @@ public class AAManagedTable { let config = AAManagedSearchConfig() - closure(s: config) + closure(config) // Creating search source @@ -130,7 +130,7 @@ public class AAManagedTable { self.isSearchAutoHide = config.isSearchAutoHide } - public func endUpdates() { + open func endUpdates() { if !isUpdating { fatalError("Table is not in editable mode") } @@ -139,23 +139,23 @@ public class AAManagedTable { // Reloading table - public func reload() { + open func reload() { self.tableView.reloadData() } - public func reload(section: Int) { - self.tableView.reloadSections(NSIndexSet(index: section), withRowAnimation: .Automatic) + open func reload(_ section: Int) { + self.tableView.reloadSections(IndexSet(integer: section), with: .automatic) } // Binding methods - public func bind(binder: AABinder) { + open func bind(_ binder: AABinder) { for s in sections { s.bind(self, binder: binder) } } - public func unbind(binder: AABinder) { + open func unbind(_ binder: AABinder) { for s in sections { s.unbind(self, binder: binder) } @@ -163,21 +163,21 @@ public class AAManagedTable { // Show/hide table - public func showTable() { + open func showTable() { if isUpdating || !fadeShowing { self.tableView.alpha = 1 } else { - UIView.animateWithDuration(0.3, animations: { () -> Void in + UIView.animate(withDuration: 0.3, animations: { () -> Void in self.tableView.alpha = 1 }) } } - public func hideTable() { + open func hideTable() { if isUpdating || !fadeShowing { self.tableView.alpha = 0 } else { - UIView.animateWithDuration(0.3, animations: { () -> Void in + UIView.animate(withDuration: 0.3, animations: { () -> Void in self.tableView.alpha = 0 }) } @@ -185,7 +185,7 @@ public class AAManagedTable { // Controller callbacks - public func controllerViewWillDisappear(animated: Bool) { + open func controllerViewWillDisappear(_ animated: Bool) { // Auto close search on leaving controller if isSearchAutoHide { @@ -195,14 +195,14 @@ public class AAManagedTable { } } - public func controllerViewDidDisappear(animated: Bool) { + open func controllerViewDidDisappear(_ animated: Bool) { // Auto close search on leaving controller // searchDisplayController?.setActive(false, animated: animated) } - public func controllerViewWillAppear(animated: Bool) { + open func controllerViewWillAppear(_ animated: Bool) { // Search bar dissapear fixing @@ -224,14 +224,14 @@ public class AAManagedTable { // Status bar styles - if (searchDisplayController != nil && searchDisplayController!.active) { + if (searchDisplayController != nil && searchDisplayController!.isActive) { // If search is active: apply search status bar style - UIApplication.sharedApplication().setStatusBarStyle(ActorSDK.sharedActor().style.searchStatusBarStyle, animated: true) + UIApplication.shared.setStatusBarStyle(ActorSDK.sharedActor().style.searchStatusBarStyle, animated: true) } else { // If search is not active: apply main status bar style - UIApplication.sharedApplication().setStatusBarStyle(ActorSDK.sharedActor().style.vcStatusBarStyle, animated: true) + UIApplication.shared.setStatusBarStyle(ActorSDK.sharedActor().style.vcStatusBarStyle, animated: true) } } } @@ -240,8 +240,8 @@ public class AAManagedTable { public extension AAManagedTable { - public func section(closure: (s: AAManagedSection) -> ()){ - closure(s: addSection(true)) + public func section(_ closure: (_ s: AAManagedSection) -> ()){ + closure(addSection(true)) } } @@ -249,15 +249,15 @@ public extension AAManagedTable { private class AMPlainTableDelegate: AMBaseTableDelegate { - @objc func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { + @objc func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return CGFloat(data.sections[section].headerHeight) } - @objc func tableView(tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { + @objc func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { return CGFloat(data.sections[section].footerHeight) } - @objc func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + @objc func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { if (data.sections[section].headerText == nil) { return UIView() } else { @@ -265,7 +265,7 @@ private class AMPlainTableDelegate: AMBaseTableDelegate { } } - @objc func tableView(tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { + @objc func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { if (data.sections[section].footerText == nil) { return UIView() } else { @@ -277,12 +277,12 @@ private class AMPlainTableDelegate: AMBaseTableDelegate { private class AMGrouppedTableDelegate: AMBaseTableDelegate { - @objc func tableView(tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) { + @objc func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) { let header: UITableViewHeaderFooterView = view as! UITableViewHeaderFooterView header.textLabel!.textColor = ActorSDK.sharedActor().style.cellHeaderColor } - @objc func tableView(tableView: UITableView, willDisplayFooterView view: UIView, forSection section: Int) { + @objc func tableView(_ tableView: UITableView, willDisplayFooterView view: UIView, forSection section: Int) { let header: UITableViewHeaderFooterView = view as! UITableViewHeaderFooterView header.textLabel!.textColor = ActorSDK.sharedActor().style.cellFooterColor } @@ -290,25 +290,25 @@ private class AMGrouppedTableDelegate: AMBaseTableDelegate { private class AMBaseTableDelegate: NSObject, UITableViewDelegate, UITableViewDataSource, UIScrollViewDelegate { - unowned private let data: AAManagedTable + unowned fileprivate let data: AAManagedTable init(data: AAManagedTable) { self.data = data } - @objc func numberOfSectionsInTableView(tableView: UITableView) -> Int { + @objc func numberOfSections(in tableView: UITableView) -> Int { return data.sections.count } - @objc func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + @objc func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return data.sections[section].numberOfItems(data) } - @objc func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { - return data.sections[indexPath.section].cellForItem(data, indexPath: indexPath) + @objc func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + return data.sections[(indexPath as NSIndexPath).section].cellForItem(data, indexPath: indexPath) } - @objc func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + @objc func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { let text = data.sections[section].headerText if text != nil { return AALocalized(text!) @@ -317,7 +317,7 @@ private class AMBaseTableDelegate: NSObject, UITableViewDelegate, UITableViewDat } } - @objc func tableView(tableView: UITableView, titleForFooterInSection section: Int) -> String? { + @objc func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { let text = data.sections[section].footerText if text != nil { return AALocalized(text!) @@ -327,92 +327,92 @@ private class AMBaseTableDelegate: NSObject, UITableViewDelegate, UITableViewDat } - @objc func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { + @objc func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { if data.fixedHeight != nil { return data.fixedHeight! } - return data.sections[indexPath.section].cellHeightForItem(data, indexPath: indexPath) + return data.sections[(indexPath as NSIndexPath).section].cellHeightForItem(data, indexPath: indexPath) } - @objc func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool { + @objc func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { if data.canEditAll != nil { return data.canEditAll! } - return (data.sections[indexPath.section].numberOfItems(data) > 0 ? data.sections[indexPath.section].canDelete(data, indexPath: indexPath) : false) + return (data.sections[(indexPath as NSIndexPath).section].numberOfItems(data) > 0 ? data.sections[(indexPath as NSIndexPath).section].canDelete(data, indexPath: indexPath) : false) } - @objc func tableView(tableView: UITableView, canMoveRowAtIndexPath indexPath: NSIndexPath) -> Bool { + @objc func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool { return false } - @objc func tableView(tableView: UITableView, editingStyleForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCellEditingStyle { + @objc func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCellEditingStyle { if data.canDeleteAll != nil { if data.canDeleteAll! { - return .Delete + return .delete } else { - return .None + return .none } } - return data.sections[indexPath.section].canDelete(data, indexPath: indexPath) ? .Delete : .None + return data.sections[(indexPath as NSIndexPath).section].canDelete(data, indexPath: indexPath) ? .delete : .none } - @objc func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) { - return data.sections[indexPath.section].delete(data, indexPath: indexPath) + @objc func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) { + return data.sections[(indexPath as NSIndexPath).section].delete(data, indexPath: indexPath) } - @objc func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { - let section = data.sections[indexPath.section] + @objc func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let section = data.sections[(indexPath as NSIndexPath).section] if section.canSelect(data, indexPath: indexPath) { if section.select(data, indexPath: indexPath) { - tableView.deselectRowAtIndexPath(indexPath, animated: true) + tableView.deselectRow(at: indexPath, animated: true) } } else { - tableView.deselectRowAtIndexPath(indexPath, animated: true) + tableView.deselectRow(at: indexPath, animated: true) } } - @objc func tableView(tableView: UITableView, canPerformAction action: Selector, forRowAtIndexPath indexPath: NSIndexPath, withSender sender: AnyObject?) -> Bool { + @objc func tableView(_ tableView: UITableView, canPerformAction action: Selector, forRowAt indexPath: IndexPath, withSender sender: Any?) -> Bool { - if action == "copy:" { - let section = data.sections[indexPath.section] + if action == #selector(UIResponderStandardEditActions.copy(_:)) { + let section = data.sections[(indexPath as NSIndexPath).section] return section.canCopy(data, indexPath: indexPath) } return false } - @objc func tableView(tableView: UITableView, shouldShowMenuForRowAtIndexPath indexPath: NSIndexPath) -> Bool { - let section = data.sections[indexPath.section] + @objc func tableView(_ tableView: UITableView, shouldShowMenuForRowAt indexPath: IndexPath) -> Bool { + let section = data.sections[(indexPath as NSIndexPath).section] return section.canCopy(data, indexPath: indexPath) } - @objc func tableView(tableView: UITableView, performAction action: Selector, forRowAtIndexPath indexPath: NSIndexPath, withSender sender: AnyObject?) { - if action == "copy:" { - let section = data.sections[indexPath.section] + @objc func tableView(_ tableView: UITableView, performAction action: Selector, forRowAt indexPath: IndexPath, withSender sender: Any?) { + if action == #selector(UIResponderStandardEditActions.copy(_:)) { + let section = data.sections[(indexPath as NSIndexPath).section] if section.canCopy(data, indexPath: indexPath) { section.copy(data, indexPath: indexPath) } } } - @objc func scrollViewDidScroll(scrollView: UIScrollView) { + @objc func scrollViewDidScroll(_ scrollView: UIScrollView) { if (data.tableView == scrollView) { - data.tableScrollClosure?(tableView: data.tableView) + data.tableScrollClosure?(data.tableView) } } } -public class AAManagedSearchConfig { +open class AAManagedSearchConfig where BindCell: AABindedSearchCell, BindCell: UITableViewCell { - public var searchList: ARBindedDisplayList? - public var searchModel: ARSearchValueModel? - public var selectAction: ((BindCell.BindData) -> ())? - public var isSearchAutoHide: Bool = true - public var didBind: ((c: BindCell, d: BindCell.BindData) -> ())? + open var searchList: ARBindedDisplayList? + open var searchModel: ARSearchValueModel? + open var selectAction: ((BindCell.BindData) -> ())? + open var isSearchAutoHide: Bool = true + open var didBind: ((_ c: BindCell, _ d: BindCell.BindData) -> ())? } -private class AAManagedSearchController: NSObject, UISearchBarDelegate, UISearchDisplayDelegate, UITableViewDataSource, UITableViewDelegate, ARDisplayList_Listener, ARValueChangedListener { +private class AAManagedSearchController: NSObject, UISearchBarDelegate, UISearchDisplayDelegate, UITableViewDataSource, UITableViewDelegate, ARDisplayList_Listener, ARValueChangedListener where BindCell: AABindedSearchCell, BindCell: UITableViewCell { let config: AAManagedSearchConfig let searchList: ARBindedDisplayList? @@ -430,25 +430,25 @@ private class AAManagedSearchController BindCell.BindData { + func objectAtIndexPath(_ indexPath: IndexPath) -> BindCell.BindData { if let ds = searchList { - return ds.itemWithIndex(jint(indexPath.row)) as! BindCell.BindData + return ds.item(with: jint((indexPath as NSIndexPath).row)) as! BindCell.BindData } else if let sm = searchModel { let list = sm.getResults().get() as! JavaUtilList - return list.getWithInt(jint(indexPath.row)) as! BindCell.BindData + return list.getWith(jint((indexPath as NSIndexPath).row)) as! BindCell.BindData } else { fatalError("No search model or search list is set!") } @@ -511,13 +511,13 @@ private class AAManagedSearchController Int { + @objc func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { if let ds = searchList { return Int(ds.size()) } else if let sm = searchModel { @@ -528,35 +528,35 @@ private class AAManagedSearchController CGFloat { + @objc func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { let item = objectAtIndexPath(indexPath) return BindCell.self.bindedCellHeight(item) } - @objc func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { + @objc func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let item = objectAtIndexPath(indexPath) let cell = tableView.dequeueCell(BindCell.self, indexPath: indexPath) as! BindCell cell.bind(item, search: nil) return cell } - @objc func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { + @objc func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let item = objectAtIndexPath(indexPath) config.selectAction!(item) } // Search updating - @objc func searchBar(searchBar: UISearchBar, textDidChange searchText: String) { + @objc func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) { if let ds = searchList { - let normalized = searchText.trim().lowercaseString + let normalized = searchText.trim().lowercased() if (normalized.length > 0) { - ds.initSearchWithQuery(normalized, withRefresh: false) + ds.initSearch(withQuery: normalized, withRefresh: false) } else { ds.initEmpty() } } else if let sm = searchModel { - sm.queryChangedWithNSString(searchText) + sm.queryChanged(with: searchText) } else { fatalError("No search model or search list is set!") } @@ -564,15 +564,15 @@ private class AAManagedSearchController UIStatusBarStyle { + open override var preferredStatusBarStyle : UIStatusBarStyle { return ActorSDK.sharedActor().style.vcStatusBarStyle } - private func styleNavBar() { + fileprivate func styleNavBar() { navigationBar.titleTextAttributes = [NSForegroundColorAttributeName: ActorSDK.sharedActor().style.navigationTitleColor] navigationBar.tintColor = ActorSDK.sharedActor().style.navigationTintColor diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/AATableViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/AATableViewController.swift index be9db17f80..01ddd22b13 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/AATableViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/AATableViewController.swift @@ -4,14 +4,14 @@ import UIKit -public class AATableViewController: AAViewController, UITableViewDataSource, UITableViewDelegate { +open class AATableViewController: AAViewController, UITableViewDataSource, UITableViewDelegate { - public let tableView: UITableView - public let tableViewStyle: UITableViewStyle + open let tableView: UITableView + open let tableViewStyle: UITableViewStyle public init(style: UITableViewStyle) { tableViewStyle = style - tableView = UITableView(frame: CGRectZero, style: tableViewStyle) + tableView = UITableView(frame: CGRect.zero, style: tableViewStyle) super.init() } @@ -19,50 +19,50 @@ public class AATableViewController: AAViewController, UITableViewDataSource, UIT fatalError("init(coder:) has not been implemented") } - public override func loadView() { + open override func loadView() { super.loadView() tableView.delegate = self tableView.dataSource = self view.addSubview(tableView) - navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: UIBarButtonItemStyle.Plain, target: nil, action: nil) + navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: UIBarButtonItemStyle.plain, target: nil, action: nil) } - public override func viewWillAppear(animated: Bool) { + open override func viewWillAppear(_ animated: Bool) { if let row = tableView.indexPathForSelectedRow { - tableView.deselectRowAtIndexPath(row, animated: animated) + tableView.deselectRow(at: row, animated: animated) } super.viewWillAppear(animated) } - public override func viewWillLayoutSubviews() { + open override func viewWillLayoutSubviews() { super.viewWillLayoutSubviews() tableView.frame = view.bounds; } - public override func setEditing(editing: Bool, animated: Bool) { + open override func setEditing(_ editing: Bool, animated: Bool) { super.setEditing(editing, animated: animated) tableView.setEditing(editing, animated: animated) } - private func placeholderViewFrame() -> CGRect { + fileprivate func placeholderViewFrame() -> CGRect { let navigationBarHeight: CGFloat = 64.5 return CGRect(x: 0, y: navigationBarHeight, width: view.bounds.size.width, height: view.bounds.size.height - navigationBarHeight) } - public func numberOfSectionsInTableView(tableView: UITableView) -> Int { + open func numberOfSections(in tableView: UITableView) -> Int { return 1 } - public func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + open func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 0 } - public func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { + open func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { return UITableViewCell() } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/AAViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/AAViewController.swift index cd523e18e3..8580069fde 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/AAViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/AAViewController.swift @@ -8,24 +8,24 @@ import RSKImageCropper import DZNWebViewController import SafariServices -public class AAViewController: UIViewController, UINavigationControllerDelegate, UIImagePickerControllerDelegate, RSKImageCropViewControllerDelegate, UIViewControllerTransitioningDelegate { +open class AAViewController: UIViewController, UINavigationControllerDelegate, UIImagePickerControllerDelegate, RSKImageCropViewControllerDelegate, UIViewControllerTransitioningDelegate { // MARK: - // MARK: Public vars var placeholder = AABigPlaceholderView(topOffset: 0) - var pendingPickClosure: ((image: UIImage) -> ())? + var pendingPickClosure: ((_ image: UIImage) -> ())? var popover: UIPopoverController? // Content type for view tracking - public var content: ACPage? + open var content: ACPage? // Data for views - public var autoTrack: Bool = false { + open var autoTrack: Bool = false { didSet { if self.autoTrack { if let u = self.uid { @@ -38,29 +38,29 @@ public class AAViewController: UIViewController, UINavigationControllerDelegate, } } - public var uid: Int! { + open var uid: Int! { didSet { if self.uid != nil { self.user = Actor.getUserWithUid(jint(self.uid)) - self.isBot = user.isBot().boolValue + self.isBot = user.isBot() } } } - public var user: ACUserVM! - public var isBot: Bool! + open var user: ACUserVM! + open var isBot: Bool! - public var gid: Int! { + open var gid: Int! { didSet { if self.gid != nil { self.group = Actor.getGroupWithGid(jint(self.gid)) } } } - public var group: ACGroupVM! + open var group: ACGroupVM! // Style - public var appStyle: ActorStyle { + open var appStyle: ActorStyle { get { return ActorSDK.sharedActor().style } @@ -69,42 +69,42 @@ public class AAViewController: UIViewController, UINavigationControllerDelegate, public init() { super.init(nibName: nil, bundle: nil) - navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: UIBarButtonItemStyle.Plain, target: nil, action: nil) + navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: UIBarButtonItemStyle.plain, target: nil, action: nil) } - public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) { + public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) - navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: UIBarButtonItemStyle.Plain, target: nil, action: nil) + navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: UIBarButtonItemStyle.plain, target: nil, action: nil) } public required init(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - public override func preferredStatusBarStyle() -> UIStatusBarStyle { + open override var preferredStatusBarStyle : UIStatusBarStyle { return ActorSDK.sharedActor().style.vcStatusBarStyle } - public func showPlaceholderWithImage(image: UIImage?, title: String?, subtitle: String?) { + open func showPlaceholderWithImage(_ image: UIImage?, title: String?, subtitle: String?) { placeholder.setImage(image, title: title, subtitle: subtitle) showPlaceholder() } - public func showPlaceholder() { + open func showPlaceholder() { if placeholder.superview == nil { placeholder.frame = view.bounds view.addSubview(placeholder) } } - public func hidePlaceholder() { + open func hidePlaceholder() { if placeholder.superview != nil { placeholder.removeFromSuperview() } } - public func shakeView(view: UIView, originalX: CGFloat) { + open func shakeView(_ view: UIView, originalX: CGFloat) { var r = view.frame r.origin.x = originalX let originalFrame = r @@ -112,11 +112,11 @@ public class AAViewController: UIViewController, UINavigationControllerDelegate, rFirst.origin.x = r.origin.x + 4 r.origin.x = r.origin.x - 4 - UIView.animateWithDuration(0.05, delay: 0.0, options: .Autoreverse, animations: { () -> Void in + UIView.animate(withDuration: 0.05, delay: 0.0, options: .autoreverse, animations: { () -> Void in view.frame = rFirst }) { (finished) -> Void in if (finished) { - UIView.animateWithDuration(0.05, delay: 0.0, options: [.Repeat, .Autoreverse], animations: { () -> Void in + UIView.animate(withDuration: 0.05, delay: 0.0, options: [.repeat, .autoreverse], animations: { () -> Void in UIView.setAnimationRepeatCount(3) view.frame = r }, completion: { (finished) -> Void in @@ -128,116 +128,116 @@ public class AAViewController: UIViewController, UINavigationControllerDelegate, } } - public func pickAvatar(takePhoto:Bool, closure: (image: UIImage) -> ()) { + open func pickAvatar(_ takePhoto:Bool, closure: @escaping (_ image: UIImage) -> ()) { self.pendingPickClosure = closure let pickerController = AAImagePickerController() - pickerController.sourceType = (takePhoto ? UIImagePickerControllerSourceType.Camera : UIImagePickerControllerSourceType.PhotoLibrary) + pickerController.sourceType = (takePhoto ? UIImagePickerControllerSourceType.camera : UIImagePickerControllerSourceType.photoLibrary) pickerController.mediaTypes = [kUTTypeImage as String] pickerController.delegate = self - self.navigationController!.presentViewController(pickerController, animated: true, completion: nil) + self.navigationController!.present(pickerController, animated: true, completion: nil) } - public override func viewWillLayoutSubviews() { + open override func viewWillLayoutSubviews() { super.viewWillLayoutSubviews() - placeholder.frame = CGRectMake(0, 64, view.bounds.width, view.bounds.height - 64) + placeholder.frame = CGRect(x: 0, y: 64, width: view.bounds.width, height: view.bounds.height - 64) } - public override func viewWillAppear(animated: Bool) { + open override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) if let c = content { ActorSDK.sharedActor().trackPageVisible(c) } if let u = uid { - Actor.onProfileOpenWithUid(jint(u)) + Actor.onProfileOpen(withUid: jint(u)) } } - public override func viewWillDisappear(animated: Bool) { + open override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) if let c = content { ActorSDK.sharedActor().trackPageHidden(c) } if let u = uid { - Actor.onProfileClosedWithUid(jint(u)) + Actor.onProfileClosed(withUid: jint(u)) } } - public func dismiss() { - dismissViewControllerAnimated(true, completion: nil) + open func dismiss() { + self.dismiss(animated: true, completion: nil) } - public func presentInNavigation(controller: UIViewController) { + open func presentInNavigation(_ controller: UIViewController) { let navigation = AANavigationController() navigation.viewControllers = [controller] - presentViewController(navigation, animated: true, completion: nil) + present(navigation, animated: true, completion: nil) } - public func openUrl(url: String) { - if let url = NSURL(string: url) { + open func openUrl(_ url: String) { + if let url = URL(string: url) { if #available(iOS 9.0, *) { - self.presentElegantViewController(SFSafariViewController(URL: url)) + self.presentElegantViewController(SFSafariViewController(url: url)) } else { - self.presentElegantViewController(AANavigationController(rootViewController: DZNWebViewController(URL: url))) + self.presentElegantViewController(AANavigationController(rootViewController: DZNWebViewController(url: url))) } } } // Image pick and crop - public func cropImage(image: UIImage) { + open func cropImage(_ image: UIImage) { let cropController = RSKImageCropViewController(image: image) cropController.delegate = self - navigationController!.presentViewController(UINavigationController(rootViewController: cropController), animated: true, completion: nil) + navigationController!.present(UINavigationController(rootViewController: cropController), animated: true, completion: nil) } - public func imageCropViewController(controller: RSKImageCropViewController, didCropImage croppedImage: UIImage, usingCropRect cropRect: CGRect) { + open func imageCropViewController(_ controller: RSKImageCropViewController, didCropImage croppedImage: UIImage, usingCropRect cropRect: CGRect) { if (pendingPickClosure != nil){ - pendingPickClosure!(image: croppedImage) + pendingPickClosure!(croppedImage) } pendingPickClosure = nil - navigationController!.dismissViewControllerAnimated(true, completion: nil) + navigationController!.dismiss(animated: true, completion: nil) } - public func imageCropViewControllerDidCancelCrop(controller: RSKImageCropViewController) { + open func imageCropViewControllerDidCancelCrop(_ controller: RSKImageCropViewController) { pendingPickClosure = nil - navigationController!.dismissViewControllerAnimated(true, completion: nil) + navigationController!.dismiss(animated: true, completion: nil) } - public func imagePickerController(picker: UIImagePickerController, didFinishPickingImage image: UIImage, editingInfo: [String : AnyObject]?) { - navigationController!.dismissViewControllerAnimated(true, completion: { () -> Void in + open func imagePickerController(_ picker: UIImagePickerController, didFinishPickingImage image: UIImage, editingInfo: [String : AnyObject]?) { + navigationController!.dismiss(animated: true, completion: { () -> Void in self.cropImage(image) }) } - public func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) { + open func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) { let image = info[UIImagePickerControllerOriginalImage] as! UIImage - navigationController!.dismissViewControllerAnimated(true, completion: { () -> Void in + navigationController!.dismiss(animated: true, completion: { () -> Void in self.cropImage(image) }) } - public func imagePickerControllerDidCancel(picker: UIImagePickerController) { + open func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { pendingPickClosure = nil - self.dismissViewControllerAnimated(true, completion: nil) + self.dismiss(animated: true, completion: nil) } - public func presentationControllerForPresentedViewController(presented: UIViewController, presentingViewController presenting: UIViewController, sourceViewController source: UIViewController) -> UIPresentationController? { - return ElegantPresentations.controller(presentedViewController: presented, presentingViewController: presenting, options: []) + open func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? { + return ElegantPresentations.controller(presentedViewController: presented, presentingViewController: presenting!, options: []) } - public func presentElegantViewController(controller: UIViewController) { + open func presentElegantViewController(_ controller: UIViewController) { if AADevice.isiPad { - controller.modalPresentationStyle = .FormSheet - presentViewController(controller, animated: true, completion: nil) + controller.modalPresentationStyle = .formSheet + present(controller, animated: true, completion: nil) } else { - controller.modalPresentationStyle = .Custom + controller.modalPresentationStyle = .custom controller.transitioningDelegate = self - presentViewController(controller, animated: true, completion: nil) + present(controller, animated: true, completion: nil) } } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/Alerts.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/Alerts.swift index 97a1f6d288..e96d685a9f 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/Alerts.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/Alerts.swift @@ -9,44 +9,44 @@ private var actionShitReference = "_action_shit" public extension UIViewController { - public func alertUser(message: String) { - let controller = UIAlertController(title: nil, message: message, preferredStyle: UIAlertControllerStyle.Alert) - controller.addAction(UIAlertAction(title: AALocalized("AlertOk"), style: UIAlertActionStyle.Cancel, handler: nil)) - self.presentViewController(controller, animated: true, completion: nil) + public func alertUser(_ message: String) { + let controller = UIAlertController(title: nil, message: message, preferredStyle: UIAlertControllerStyle.alert) + controller.addAction(UIAlertAction(title: AALocalized("AlertOk"), style: UIAlertActionStyle.cancel, handler: nil)) + self.present(controller, animated: true, completion: nil) } - public func alertUser(message: String, tapYes: ()->()) { - let controller = UIAlertController(title: nil, message: message, preferredStyle: UIAlertControllerStyle.Alert) - controller.addAction(UIAlertAction(title: AALocalized("AlertOk"), style: UIAlertActionStyle.Cancel, handler: { (alertView) -> () in + public func alertUser(_ message: String, tapYes: @escaping ()->()) { + let controller = UIAlertController(title: nil, message: message, preferredStyle: UIAlertControllerStyle.alert) + controller.addAction(UIAlertAction(title: AALocalized("AlertOk"), style: UIAlertActionStyle.cancel, handler: { (alertView) -> () in tapYes() })) - self.presentViewController(controller, animated: true, completion: nil) + self.present(controller, animated: true, completion: nil) } - public func confirmAlertUser(message: String, action: String, tapYes: ()->(), tapNo: (()->())? = nil) { - let controller = UIAlertController(title: nil, message: AALocalized(message), preferredStyle: UIAlertControllerStyle.Alert) - controller.addAction(UIAlertAction(title: AALocalized(action), style: UIAlertActionStyle.Default, handler: { (alertView) -> () in + public func confirmAlertUser(_ message: String, action: String, tapYes: @escaping ()->(), tapNo: (()->())? = nil) { + let controller = UIAlertController(title: nil, message: AALocalized(message), preferredStyle: UIAlertControllerStyle.alert) + controller.addAction(UIAlertAction(title: AALocalized(action), style: UIAlertActionStyle.default, handler: { (alertView) -> () in tapYes() })) - controller.addAction(UIAlertAction(title: AALocalized("AlertCancel"), style: UIAlertActionStyle.Cancel, handler: { (alertView) -> () in + controller.addAction(UIAlertAction(title: AALocalized("AlertCancel"), style: UIAlertActionStyle.cancel, handler: { (alertView) -> () in tapNo?() })) - self.presentViewController(controller, animated: true, completion: nil) + self.present(controller, animated: true, completion: nil) } - public func confirmAlertUserDanger(message: String, action: String, tapYes: ()->(), tapNo: (()->())? = nil) { - let controller = UIAlertController(title: nil, message: AALocalized(message), preferredStyle: UIAlertControllerStyle.Alert) - controller.addAction(UIAlertAction(title: AALocalized(action), style: UIAlertActionStyle.Destructive, handler: { (alertView) -> () in + public func confirmAlertUserDanger(_ message: String, action: String, tapYes: @escaping ()->(), tapNo: (()->())? = nil) { + let controller = UIAlertController(title: nil, message: AALocalized(message), preferredStyle: UIAlertControllerStyle.alert) + controller.addAction(UIAlertAction(title: AALocalized(action), style: UIAlertActionStyle.destructive, handler: { (alertView) -> () in tapYes() })) - controller.addAction(UIAlertAction(title: AALocalized("AlertCancel"), style: UIAlertActionStyle.Cancel, handler: { (alertView) -> () in + controller.addAction(UIAlertAction(title: AALocalized("AlertCancel"), style: UIAlertActionStyle.cancel, handler: { (alertView) -> () in tapNo?() })) - self.presentViewController(controller, animated: true, completion: nil) + self.present(controller, animated: true, completion: nil) } - public func confirmDangerSheetUser(action: String, tapYes: ()->(), tapNo: (()->())?) { - showActionSheet(nil, buttons: [], cancelButton: "AlertCancel", destructButton: action, sourceView: UIView(), sourceRect: CGRectZero) { (index) -> () in + public func confirmDangerSheetUser(_ action: String, tapYes: @escaping ()->(), tapNo: (()->())?) { + showActionSheet(nil, buttons: [], cancelButton: "AlertCancel", destructButton: action, sourceView: UIView(), sourceRect: CGRect.zero) { (index) -> () in if index == -2 { tapYes() } else { @@ -55,55 +55,55 @@ public extension UIViewController { } } - public func showActionSheet(title: String?, buttons: [String], cancelButton: String?, destructButton: String?, sourceView: UIView, sourceRect: CGRect, tapClosure: (index: Int) -> ()) { + public func showActionSheet(_ title: String?, buttons: [String], cancelButton: String?, destructButton: String?, sourceView: UIView, sourceRect: CGRect, tapClosure: @escaping (_ index: Int) -> ()) { - let controller = UIAlertController(title: title, message: nil, preferredStyle: UIAlertControllerStyle.ActionSheet) + let controller = UIAlertController(title: title, message: nil, preferredStyle: UIAlertControllerStyle.actionSheet) if cancelButton != nil { - controller.addAction(UIAlertAction(title: AALocalized(cancelButton), style: UIAlertActionStyle.Cancel, handler: { (alertView) -> () in - tapClosure(index: -1) + controller.addAction(UIAlertAction(title: AALocalized(cancelButton), style: UIAlertActionStyle.cancel, handler: { (alertView) -> () in + tapClosure(-1) })) } if destructButton != nil { - controller.addAction(UIAlertAction(title: AALocalized(destructButton), style: UIAlertActionStyle.Destructive, handler: { (alertView) -> () in - tapClosure(index: -2) + controller.addAction(UIAlertAction(title: AALocalized(destructButton), style: UIAlertActionStyle.destructive, handler: { (alertView) -> () in + tapClosure(-2) })) } for b in 0.. () in - tapClosure(index: b) + controller.addAction(UIAlertAction(title: AALocalized(buttons[b]), style: UIAlertActionStyle.default, handler: { (alertView) -> () in + tapClosure(b) })) } - self.presentViewController(controller, animated: true, completion: nil) + self.present(controller, animated: true, completion: nil) } - func showActionSheet(buttons: [String], cancelButton: String?, destructButton: String?, sourceView: UIView, sourceRect: CGRect, tapClosure: (index: Int) -> ()) { + func showActionSheet(_ buttons: [String], cancelButton: String?, destructButton: String?, sourceView: UIView, sourceRect: CGRect, tapClosure: @escaping (_ index: Int) -> ()) { showActionSheet(nil, buttons:buttons, cancelButton: cancelButton, destructButton: destructButton, sourceView: sourceView, sourceRect:sourceRect, tapClosure: tapClosure) } - func startEditText(@noescape closure: (AAEditTextControllerConfig) -> ()) { + func startEditText(_ closure: (AAEditTextControllerConfig) -> ()) { let config = AAEditTextControllerConfig() closure(config) config.check() let controller = AANavigationController(rootViewController: AAEditTextController(config: config)) if (AADevice.isiPad) { - controller.modalPresentationStyle = .FormSheet + controller.modalPresentationStyle = .formSheet } - self.presentViewController(controller, animated: true, completion: nil) + self.present(controller, animated: true, completion: nil) } - func startEditField(@noescape closure: (c: AAEditFieldControllerConfig) -> ()) { + func startEditField(_ closure: (_ c: AAEditFieldControllerConfig) -> ()) { let config = AAEditFieldControllerConfig() - closure(c: config) + closure(config) config.check() let controller = AANavigationController(rootViewController: AAEditFieldController(config: config)) if (AADevice.isiPad) { - controller.modalPresentationStyle = .FormSheet + controller.modalPresentationStyle = .formSheet } - self.presentViewController(controller, animated: true, completion: nil) + self.present(controller, animated: true, completion: nil) } } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/Executions.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/Executions.swift index 67bac6ba36..0ff5c7dad5 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/Executions.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/Executions.swift @@ -6,16 +6,16 @@ import Foundation import MBProgressHUD public enum AAExecutionType { - case Normal - case Hidden - case Safe + case normal + case hidden + case safe } -public class AAMenuBuilder { +open class AAMenuBuilder { - public var tapClosure: ((index: Int) -> ())! - public var items = [String]() - public var closures: [(()->())?] = [] + open var tapClosure: ((_ index: Int) -> ())! + open var items = [String]() + open var closures: [(()->())?] = [] public init() { @@ -26,49 +26,49 @@ public class AAMenuBuilder { } } - public func add(title: String, closure: (()->())?) { + open func add(_ title: String, closure: (()->())?) { items.append(title) closures.append(closure) } } -public class AAExecutions { +open class AAExecutions { - public class func execute(promise: ARPromise) { + open class func execute(_ promise: ARPromise) { executePromise(promise) } - public class func execute(command: ACCommand) { + open class func execute(_ command: ACCommand) { execute(command, successBlock: nil, failureBlock: nil) } - public class func executePromise(promice: ARPromise){ + open class func executePromise(_ promice: ARPromise){ promice.startUserAction() } - public class func executePromise(promice: ARPromise, successBlock: ((val: Any?) -> Void)?, failureBlock: ((val: Any?) -> Void)? ){ + open class func executePromise(_ promice: ARPromise, successBlock: ((_ val: Any?) -> Void)?, failureBlock: ((_ val: Any?) -> Void)? ){ promice.startUserAction() promice.then { result in - successBlock!(val: result) + successBlock!(result) } } - public class func execute(command: ACCommand, type: AAExecutionType = .Normal, ignore: [String] = [], successBlock: ((val: Any?) -> Void)?, failureBlock: ((val: Any?) -> Void)?) { + open class func execute(_ command: ACCommand, type: AAExecutionType = .normal, ignore: [String] = [], successBlock: ((_ val: Any?) -> Void)?, failureBlock: ((_ val: Any?) -> Void)?) { var hud: MBProgressHUD? - if type != .Hidden { + if type != .hidden { hud = showProgress() } - command.startWithCallback(AACommandCallback(result: { (val:Any?) -> () in + command.start(with: AACommandCallback(result: { (val:Any?) -> () in dispatchOnUi { hud?.hide(true) - successBlock?(val: val) + successBlock?(val) } }, error: { (val) -> () in dispatchOnUi { hud?.hide(true) - if type == .Safe { + if type == .safe { // If unknown error, just try again var tryAgain = true @@ -76,7 +76,7 @@ public class AAExecutions { // If is in ignore list, just return to UI if ignore.contains(exception.tag) { - failureBlock?(val: val) + failureBlock?(val) return } @@ -86,32 +86,32 @@ public class AAExecutions { // Showing alert if tryAgain { - errorWithError(val, rep: { () -> () in + errorWithError(val!, rep: { () -> () in AAExecutions.execute(command, type: type, successBlock: successBlock, failureBlock: failureBlock) }, cancel: { () -> () in - failureBlock?(val: val) + failureBlock?(val) }) } else { - errorWithError(val, cancel: { () -> () in - failureBlock?(val: val) + errorWithError(val!, cancel: { () -> () in + failureBlock?(val) }) } } else { - failureBlock?(val: val) + failureBlock?(val) } } })) } - public class func errorWithError(e: AnyObject, rep:(()->())? = nil, cancel:(()->())? = nil) { + open class func errorWithError(_ e: AnyObject, rep:(()->())? = nil, cancel:(()->())? = nil) { error(Actor.getFormatter().formatErrorTextWithError(e), rep: rep, cancel: cancel) } - public class func errorWithTag(tag: String, rep:(()->())? = nil, cancel:(()->())? = nil) { - error(Actor.getFormatter().formatErrorTextWithTag(tag), rep: rep, cancel: cancel) + open class func errorWithTag(_ tag: String, rep:(()->())? = nil, cancel:(()->())? = nil) { + error(Actor.getFormatter().formatErrorText(withTag: tag), rep: rep, cancel: cancel) } - public class func error(message: String, rep:(()->())? = nil, cancel:(()->())? = nil) { + open class func error(_ message: String, rep:(()->())? = nil, cancel:(()->())? = nil) { if rep != nil { let d = UIAlertViewBlock(clickedClosure: { (index) -> () in if index > 0 { @@ -140,13 +140,13 @@ public class AAExecutions { } } - class private func showProgress() -> MBProgressHUD { - let window = UIApplication.sharedApplication().windows[1] + class fileprivate func showProgress() -> MBProgressHUD { + let window = UIApplication.shared.windows[1] let hud = MBProgressHUD(window: window) - hud.mode = MBProgressHUDMode.Indeterminate + hud.mode = MBProgressHUDMode.indeterminate hud.removeFromSuperViewOnHide = true window.addSubview(hud) - window.bringSubviewToFront(hud) + window.bringSubview(toFront: hud) hud.show(true) return hud } @@ -156,55 +156,55 @@ private var alertViewBlockReference = "_block_reference" @objc private class UIAlertViewBlock: NSObject, UIAlertViewDelegate { - private let clickedClosure: ((index: Int) -> ()) + fileprivate let clickedClosure: ((_ index: Int) -> ()) - init(clickedClosure: ((index: Int) -> ())) { + init(clickedClosure: @escaping ((_ index: Int) -> ())) { self.clickedClosure = clickedClosure } - @objc private func alertView(alertView: UIAlertView, clickedButtonAtIndex buttonIndex: Int) { - clickedClosure(index: buttonIndex) + @objc fileprivate func alertView(_ alertView: UIAlertView, clickedButtonAt buttonIndex: Int) { + clickedClosure(buttonIndex) } - @objc private func alertViewCancel(alertView: UIAlertView) { - clickedClosure(index: -1) + @objc fileprivate func alertViewCancel(_ alertView: UIAlertView) { + clickedClosure(-1) } } public extension UIViewController { - public func execute(command: ACCommand) { + public func execute(_ command: ACCommand) { AAExecutions.execute(command) } - public func executePromise(promise: ARPromise) -> ARPromise { + public func executePromise(_ promise: ARPromise) -> ARPromise { AAExecutions.execute(promise) return promise } - public func executePromise(promise: ARPromise, successBlock: ((val: Any?) -> Void)?, failureBlock: ((val: Any?) -> Void)?) { + public func executePromise(_ promise: ARPromise, successBlock: ((_ val: Any?) -> Void)?, failureBlock: ((_ val: Any?) -> Void)?) { AAExecutions.executePromise(promise, successBlock: successBlock, failureBlock: failureBlock) } - public func execute(command: ACCommand, successBlock: ((val: Any?) -> Void)?, failureBlock: ((val: Any?) -> Void)?) { + public func execute(_ command: ACCommand, successBlock: ((_ val: Any?) -> Void)?, failureBlock: ((_ val: Any?) -> Void)?) { AAExecutions.execute(command, successBlock: successBlock, failureBlock: failureBlock) } - public func execute(command: ACCommand, successBlock: ((val: Any?) -> Void)?) { + public func execute(_ command: ACCommand, successBlock: ((_ val: Any?) -> Void)?) { AAExecutions.execute(command, successBlock: successBlock, failureBlock: nil) } - public func executeSafe(command: ACCommand, ignore: [String] = [], successBlock: ((val: Any?) -> Void)? = nil) { - AAExecutions.execute(command, type: .Safe, ignore: ignore, successBlock: successBlock, failureBlock: { (val) -> () in - successBlock?(val: nil) + public func executeSafe(_ command: ACCommand, ignore: [String] = [], successBlock: ((_ val: Any?) -> Void)? = nil) { + AAExecutions.execute(command, type: .safe, ignore: ignore, successBlock: successBlock, failureBlock: { (val) -> () in + successBlock?(nil) }) } - public func executeSafeOnlySuccess(command: ACCommand, successBlock: ((val: Any?) -> Void)?) { - AAExecutions.execute(command, type: .Safe, ignore: [], successBlock: successBlock, failureBlock: nil) + public func executeSafeOnlySuccess(_ command: ACCommand, successBlock: ((_ val: Any?) -> Void)?) { + AAExecutions.execute(command, type: .safe, ignore: [], successBlock: successBlock, failureBlock: nil) } - public func executeHidden(command: ACCommand, successBlock: ((val: Any?) -> Void)? = nil) { - AAExecutions.execute(command, type: .Hidden, successBlock: successBlock, failureBlock: nil) + public func executeHidden(_ command: ACCommand, successBlock: ((_ val: Any?) -> Void)? = nil) { + AAExecutions.execute(command, type: .hidden, successBlock: successBlock, failureBlock: nil) } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/ManagedAlerts.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/ManagedAlerts.swift index 7da126bc71..bb19fb9792 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/ManagedAlerts.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/ManagedAlerts.swift @@ -6,51 +6,51 @@ import Foundation public extension UIViewController { - public func alertSheet(@noescape closure: (a: AAAlertSetting) -> ()) { + public func alertSheet(_ closure: (_ a: AAAlertSetting) -> ()) { let s = AAAlertSetting() - closure(a: s) + closure(s) - let controller = UIAlertController(title: AALocalized(s.title), message: AALocalized(s.message), preferredStyle: .ActionSheet) + let controller = UIAlertController(title: AALocalized(s.title), message: AALocalized(s.message), preferredStyle: .actionSheet) for i in s.actions { - controller.addAction(UIAlertAction(title: AALocalized(i.title), style: i.isDestructive ? UIAlertActionStyle.Destructive : UIAlertActionStyle.Default, handler: { (c) -> Void in + controller.addAction(UIAlertAction(title: AALocalized(i.title), style: i.isDestructive ? UIAlertActionStyle.destructive : UIAlertActionStyle.default, handler: { (c) -> Void in i.closure() })) } - controller.addAction(UIAlertAction(title: AALocalized("AlertCancel"), style: .Cancel, handler: nil)) + controller.addAction(UIAlertAction(title: AALocalized("AlertCancel"), style: .cancel, handler: nil)) - presentViewController(controller, animated: true, completion: nil) + present(controller, animated: true, completion: nil) } - public func confirmDestructive(message: String, action: String, yes: ()->()) { - let controller = UIAlertController(title: nil, message: message, preferredStyle: .Alert) - controller.addAction(UIAlertAction(title: action, style: .Destructive, handler: { (act) -> Void in + public func confirmDestructive(_ message: String, action: String, yes: @escaping ()->()) { + let controller = UIAlertController(title: nil, message: message, preferredStyle: .alert) + controller.addAction(UIAlertAction(title: action, style: .destructive, handler: { (act) -> Void in yes() })) - controller.addAction(UIAlertAction(title: AALocalized("AlertCancel"), style: .Cancel, handler: nil)) - presentViewController(controller, animated: true, completion: nil) + controller.addAction(UIAlertAction(title: AALocalized("AlertCancel"), style: .cancel, handler: nil)) + present(controller, animated: true, completion: nil) } } -public class AAAlertSetting { +open class AAAlertSetting { - public var cancel: String! - public var title: String! - public var message: String! + open var cancel: String! + open var title: String! + open var message: String! - private var actions = [AlertActions]() + fileprivate var actions = [AlertActions]() - public func action(title: String, closure: ()->()) { + open func action(_ title: String, closure: @escaping ()->()) { let a = AlertActions() a.title = title a.closure = closure actions.append(a) } - public func destructive(title: String, closure: ()->()) { + open func destructive(_ title: String, closure: @escaping ()->()) { let a = AlertActions() a.title = title a.closure = closure @@ -63,4 +63,4 @@ class AlertActions { var isDestructive = false var title: String! var closure: (()->())! -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/ManagedBindedCells.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/ManagedBindedCells.swift index acd096c092..2e52b1c5cb 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/ManagedBindedCells.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/ManagedBindedCells.swift @@ -8,108 +8,108 @@ public protocol AABindedCell { associatedtype BindData - static func bindedCellHeight(table: AAManagedTable, item: BindData) -> CGFloat + static func bindedCellHeight(_ table: AAManagedTable, item: BindData) -> CGFloat - func bind(item: BindData, table: AAManagedTable, index: Int, totalCount: Int) + func bind(_ item: BindData, table: AAManagedTable, index: Int, totalCount: Int) } public protocol AABindedSearchCell { associatedtype BindData - static func bindedCellHeight(item: BindData) -> CGFloat + static func bindedCellHeight(_ item: BindData) -> CGFloat - func bind(item: BindData, search: String?) + func bind(_ item: BindData, search: String?) } -public class AABindedRows: NSObject, AAManagedRange, ARDisplayList_AppleChangeListener, ARDisplayList_Listener { +open class AABindedRows: NSObject, AAManagedRange, ARDisplayList_AppleChangeListener, ARDisplayList_Listener where BindCell: UITableViewCell, BindCell: AABindedCell { - public var topOffset: Int = 0 + open var topOffset: Int = 0 - public var displayList: ARBindedDisplayList! + open var displayList: ARBindedDisplayList! - public var selectAction: ((BindCell.BindData) -> Bool)? + open var selectAction: ((BindCell.BindData) -> Bool)? - public var canEditAction: ((BindCell.BindData) -> Bool)? + open var canEditAction: ((BindCell.BindData) -> Bool)? - public var editAction: ((BindCell.BindData) -> ())? + open var editAction: ((BindCell.BindData) -> ())? - public var didBind: ((BindCell, BindCell.BindData) -> ())? + open var didBind: ((BindCell, BindCell.BindData) -> ())? - public var autoHide = true + open var autoHide = true - public var animated = false + open var animated = false - public var differental = false + open var differental = false - private var table: AAManagedTable! + fileprivate var table: AAManagedTable! - private var lastItemsCount: Int = 0 + fileprivate var lastItemsCount: Int = 0 - private let cellReuseId = "Bind:\(BindCell.self)" + fileprivate let cellReuseId = "Bind:\(BindCell.self)" // Initing Table - public func initTable(table: AAManagedTable) { - table.tableView.registerClass(BindCell.self, forCellReuseIdentifier: cellReuseId) + open func initTable(_ table: AAManagedTable) { + table.tableView.register(BindCell.self, forCellReuseIdentifier: cellReuseId) } // Total items count - public func rangeNumberOfItems(table: AAManagedTable) -> Int { + open func rangeNumberOfItems(_ table: AAManagedTable) -> Int { return lastItemsCount } // Cells - public func rangeCellHeightForItem(table: AAManagedTable, indexPath: AARangeIndexPath) -> CGFloat { - let data = displayList.itemWithIndex(jint(indexPath.item)) as! BindCell.BindData + open func rangeCellHeightForItem(_ table: AAManagedTable, indexPath: AARangeIndexPath) -> CGFloat { + let data = displayList.item(with: jint(indexPath.item)) as! BindCell.BindData return BindCell.self.bindedCellHeight(table, item: data) } - public func rangeCellForItem(table: AAManagedTable, indexPath: AARangeIndexPath) -> UITableViewCell { - let data = displayList.itemWithIndex(jint(indexPath.item)) as! BindCell.BindData - let cell = self.table.tableView.dequeueReusableCellWithIdentifier(cellReuseId, forIndexPath: indexPath.indexPath) as! BindCell + open func rangeCellForItem(_ table: AAManagedTable, indexPath: AARangeIndexPath) -> UITableViewCell { + let data = displayList.item(with: jint(indexPath.item)) as! BindCell.BindData + let cell = self.table.tableView.dequeueReusableCell(withIdentifier: cellReuseId, for: indexPath.indexPath as IndexPath) as! BindCell cell.bind(data, table: table, index: indexPath.item, totalCount: lastItemsCount) - displayList.touchWithIndex(jint(indexPath.item)) + displayList.touch(with: jint(indexPath.item)) didBind?(cell, data) return cell } // Select - public func rangeCanSelect(table: AAManagedTable, indexPath: AARangeIndexPath) -> Bool { + open func rangeCanSelect(_ table: AAManagedTable, indexPath: AARangeIndexPath) -> Bool { return selectAction != nil } - public func rangeSelect(table: AAManagedTable, indexPath: AARangeIndexPath) -> Bool { + open func rangeSelect(_ table: AAManagedTable, indexPath: AARangeIndexPath) -> Bool { - return selectAction!(displayList.itemWithIndex(jint(indexPath.item)) as! BindCell.BindData) + return selectAction!(displayList.item(with: jint(indexPath.item)) as! BindCell.BindData) } // Delete - public func rangeCanDelete(table: AAManagedTable, indexPath: AARangeIndexPath) -> Bool { + open func rangeCanDelete(_ table: AAManagedTable, indexPath: AARangeIndexPath) -> Bool { if canEditAction != nil { - return canEditAction!(displayList.itemWithIndex(jint(indexPath.item)) as! BindCell.BindData) + return canEditAction!(displayList.item(with: jint(indexPath.item)) as! BindCell.BindData) } return false } - public func rangeDelete(table: AAManagedTable, indexPath: AARangeIndexPath) { - editAction!(displayList.itemWithIndex(jint(indexPath.item)) as! BindCell.BindData) + open func rangeDelete(_ table: AAManagedTable, indexPath: AARangeIndexPath) { + editAction!(displayList.item(with: jint(indexPath.item)) as! BindCell.BindData) } // Binding - public func rangeBind(table: AAManagedTable, binder: AABinder) { + open func rangeBind(_ table: AAManagedTable, binder: AABinder) { self.table = table if differental { displayList.addAppleListener(self) } else { - displayList.addListener(self) + displayList.add(self) } lastItemsCount = Int(displayList.size()) @@ -117,7 +117,7 @@ public class AABindedRows= topOffset { - let data = displayList.itemWithIndex(jint(index.row - topOffset)) as! BindCell.BindData - (cell as! BindCell).bind(data, table: table, index: index.row - topOffset, totalCount: Int(displayList.size())) + if (index as NSIndexPath).section == 0 && (index as NSIndexPath).row >= topOffset { + let data = displayList.item(with: jint((index as NSIndexPath).row - topOffset)) as! BindCell.BindData + (cell as! BindCell).bind(data, table: table, index: (index as NSIndexPath).row - topOffset, totalCount: Int(displayList.size())) } } } @@ -143,7 +143,7 @@ public class AABindedRows 0 { @@ -170,27 +170,27 @@ public class AABindedRows 0 { - var rows = [NSIndexPath]() + var rows = [IndexPath]() for i in 0.. 0 { - var rows = [NSIndexPath]() + var rows = [IndexPath]() for i in 0.. 0 { for i in 0..= topOffset { - let data = displayList.itemWithIndex(jint(index.row - topOffset)) as! BindCell.BindData - (cell as! BindCell).bind(data, table: table, index: index.row - topOffset, totalCount: Int(displayList.size())) + if (index as NSIndexPath).section == 0 && (index as NSIndexPath).row >= topOffset { + let data = displayList.item(with: jint((index as NSIndexPath).row - topOffset)) as! BindCell.BindData + (cell as! BindCell).bind(data, table: table, index: (index as NSIndexPath).row - topOffset, totalCount: Int(displayList.size())) } } } @@ -244,7 +244,7 @@ public class AABindedRows(@noescape closure: (r: AABindedRows) -> ()) -> AABindedRows { + public func binded(_ closure: (_ r: AABindedRows) -> ()) -> AABindedRows where T: UITableViewCell, T: AABindedCell { let topOffset = numberOfItems(self.table) let r = AABindedRows() r.topOffset = topOffset regions.append(r) - closure(r: r) + closure(r) r.checkInstallation() r.initTable(self.table) return r diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/ManagedCells.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/ManagedCells.swift index 8446d32cb7..f0f1e8c1a8 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/ManagedCells.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/ManagedCells.swift @@ -6,21 +6,21 @@ import Foundation // Edit Row -public class AAEditRow: AAManagedRow, UITextFieldDelegate { +open class AAEditRow: AAManagedRow, UITextFieldDelegate { - public var prefix: String? - public var text: String? - public var placeholder: String? - public var returnKeyType = UIReturnKeyType.Default - public var autocorrectionType = UITextAutocorrectionType.Default - public var autocapitalizationType = UITextAutocapitalizationType.Sentences - public var returnAction: (()->())? + open var prefix: String? + open var text: String? + open var placeholder: String? + open var returnKeyType = UIReturnKeyType.default + open var autocorrectionType = UITextAutocorrectionType.default + open var autocapitalizationType = UITextAutocapitalizationType.sentences + open var returnAction: (()->())? - public override func rangeCellHeightForItem(table: AAManagedTable, indexPath: AARangeIndexPath) -> CGFloat { + open override func rangeCellHeightForItem(_ table: AAManagedTable, indexPath: AARangeIndexPath) -> CGFloat { return 44 } - public override func rangeCellForItem(table: AAManagedTable, indexPath: AARangeIndexPath) -> UITableViewCell { + open override func rangeCellForItem(_ table: AAManagedTable, indexPath: AARangeIndexPath) -> UITableViewCell { let res = table.dequeueCell(indexPath.indexPath) as AAEditCell res.textField.text = text @@ -35,28 +35,28 @@ public class AAEditRow: AAManagedRow, UITextFieldDelegate { res.textField.returnKeyType = returnKeyType res.textField.autocapitalizationType = autocapitalizationType res.textField.delegate = self - res.textField.removeTarget(nil, action: nil, forControlEvents: .AllEvents) - res.textField.addTarget(self, action: #selector(AAEditRow.textFieldDidChange(_:)), forControlEvents: .EditingChanged) + res.textField.removeTarget(nil, action: nil, for: .allEvents) + res.textField.addTarget(self, action: #selector(AAEditRow.textFieldDidChange(_:)), for: .editingChanged) if prefix != nil { res.textPrefix.text = prefix - res.textPrefix.hidden = false + res.textPrefix.isHidden = false } else { - res.textPrefix.hidden = true + res.textPrefix.isHidden = true } return res } - public func textFieldDidChange(textField: UITextField) { + open func textFieldDidChange(_ textField: UITextField) { text = textField.text } - @objc public func textFieldDidEndEditing(textField: UITextField) { + @objc open func textFieldDidEndEditing(_ textField: UITextField) { text = textField.text } - public func textFieldShouldReturn(textField: UITextField) -> Bool { + open func textFieldShouldReturn(_ textField: UITextField) -> Bool { returnAction?() return false } @@ -64,10 +64,10 @@ public class AAEditRow: AAManagedRow, UITextFieldDelegate { public extension AAManagedSection { - public func edit(@noescape closure: (r: AAEditRow) -> ()) -> AAEditRow { + public func edit(_ closure: (_ r: AAEditRow) -> ()) -> AAEditRow { let r = AAEditRow() regions.append(r) - closure(r: r) + closure(r) r.initTable(self.table) return r } @@ -75,29 +75,29 @@ public extension AAManagedSection { // Titled Row -public class AATitledRow: AAManagedRow { +open class AATitledRow: AAManagedRow { - public var title: String? - public var subtitle: String? + open var title: String? + open var subtitle: String? - public var isAction: Bool = false - public var accessoryType = UITableViewCellAccessoryType.None + open var isAction: Bool = false + open var accessoryType = UITableViewCellAccessoryType.none - public var bindAction: ((r: AATitledRow)->())? + open var bindAction: ((_ r: AATitledRow)->())? // Cell - public override func rangeCellHeightForItem(table: AAManagedTable, indexPath: AARangeIndexPath) -> CGFloat { + open override func rangeCellHeightForItem(_ table: AAManagedTable, indexPath: AARangeIndexPath) -> CGFloat { return 55 } - public override func rangeCellForItem(table: AAManagedTable, indexPath: AARangeIndexPath) -> UITableViewCell { + open override func rangeCellForItem(_ table: AAManagedTable, indexPath: AARangeIndexPath) -> UITableViewCell { let res = table.dequeueTitledCell(indexPath.indexPath) bindCell(res) return res } - public func bindCell(res: AATitledCell) { + open func bindCell(_ res: AATitledCell) { res.titleLabel.text = title res.contentLabel.text = subtitle res.accessoryType = accessoryType @@ -112,18 +112,18 @@ public class AATitledRow: AAManagedRow { // Copy - public override func rangeCopyData(table: AAManagedTable, indexPath: AARangeIndexPath) -> String? { + open override func rangeCopyData(_ table: AAManagedTable, indexPath: AARangeIndexPath) -> String? { return isAction ? nil : subtitle } // Binding - public override func reload() { + open override func reload() { - bindAction?(r: self) + bindAction?(self) if let p = indexPath, let s = section { - if let cell = s.table.tableView.cellForRowAtIndexPath(p) as? AATitledCell { + if let cell = s.table.tableView.cellForRow(at: p) as? AATitledCell { bindCell(cell) } } @@ -132,32 +132,32 @@ public class AATitledRow: AAManagedRow { public extension AAManagedSection { - private func titled() -> AATitledRow { + fileprivate func titled() -> AATitledRow { let r = AATitledRow() let itemsCount = numberOfItems(table) regions.append(r) - r.indexPath = NSIndexPath(forRow: itemsCount, inSection: index) + r.indexPath = IndexPath(row: itemsCount, section: index) r.section = self r.initTable(self.table) return r } - public func titled(@noescape closure: (r: AATitledRow) -> ()) -> AATitledRow { + public func titled(_ closure: (_ r: AATitledRow) -> ()) -> AATitledRow { let r = titled() - closure(r: r) + closure(r) r.initTable(self.table) return r } - public func titled(title: String, @noescape closure: (r: AATitledRow) -> ()) -> AATitledRow { + public func titled(_ title: String, closure: (_ r: AATitledRow) -> ()) -> AATitledRow { let r = titled() r.title = AALocalized(title) - closure(r: r) + closure(r) r.initTable(self.table) return r } - public func titled(title: String, content: String) -> AATitledRow { + public func titled(_ title: String, content: String) -> AATitledRow { let r = titled() r.title = AALocalized(title) r.subtitle = content @@ -168,44 +168,44 @@ public extension AAManagedSection { // Text Row -public class AATextRow: AAManagedRow { +open class AATextRow: AAManagedRow { - public var title: String? - public var content: String? + open var title: String? + open var content: String? - public var isAction: Bool = false - public var navigate: Bool = false + open var isAction: Bool = false + open var navigate: Bool = false - public var bindAction: ((r: AATextRow)->())? + open var bindAction: ((_ r: AATextRow)->())? // Cell - public override func rangeCellHeightForItem(table: AAManagedTable, indexPath: AARangeIndexPath) -> CGFloat { + open override func rangeCellHeightForItem(_ table: AAManagedTable, indexPath: AARangeIndexPath) -> CGFloat { return AATextCell.measure(title, text: content!, width: table.tableView.width, enableNavigation: navigate) } - public override func rangeCellForItem(table: AAManagedTable, indexPath: AARangeIndexPath) -> UITableViewCell { + open override func rangeCellForItem(_ table: AAManagedTable, indexPath: AARangeIndexPath) -> UITableViewCell { let res = table.dequeueTextCell(indexPath.indexPath) res.setContent(title, content: content, isAction: isAction) if navigate { - res.accessoryType = UITableViewCellAccessoryType.DisclosureIndicator + res.accessoryType = UITableViewCellAccessoryType.disclosureIndicator } else { - res.accessoryType = UITableViewCellAccessoryType.None + res.accessoryType = UITableViewCellAccessoryType.none } return res } // Copy - public override func rangeCopyData(table: AAManagedTable, indexPath: AARangeIndexPath) -> String? { + open override func rangeCopyData(_ table: AAManagedTable, indexPath: AARangeIndexPath) -> String? { return isAction ? nil : content } // Binding - public override func reload() { + open override func reload() { - bindAction?(r: self) + bindAction?(self) super.reload() } @@ -213,34 +213,34 @@ public class AATextRow: AAManagedRow { public extension AAManagedSection { - private func text() -> AATextRow { + fileprivate func text() -> AATextRow { let r = AATextRow() let itemsCount = numberOfItems(table) regions.append(r) - r.indexPath = NSIndexPath(forRow: itemsCount, inSection: index) + r.indexPath = IndexPath(row: itemsCount, section: index) r.section = self r.initTable(self.table) return r } - public func text(@noescape closure: (r: AATextRow) -> ()) -> AATextRow { + public func text(_ closure: (_ r: AATextRow) -> ()) -> AATextRow { let r = text() - closure(r: r) - r.bindAction?(r: r) + closure(r) + r.bindAction?(r) r.initTable(self.table) return r } - public func text(title: String?, @noescape closure: (r: AATextRow) -> ()) -> AATextRow { + public func text(_ title: String?, closure: (_ r: AATextRow) -> ()) -> AATextRow { let r = text() r.title = AALocalized(title) - closure(r: r) - r.bindAction?(r: r) + closure(r) + r.bindAction?(r) r.initTable(self.table) return r } - public func text(title: String?, content: String) -> AATextRow { + public func text(_ title: String?, content: String) -> AATextRow { let r = text() r.title = AALocalized(title) r.content = content @@ -248,7 +248,7 @@ public extension AAManagedSection { return r } - public func text(content: String) -> AATextRow { + public func text(_ content: String) -> AATextRow { let r = text() r.title = nil r.content = content @@ -260,30 +260,30 @@ public extension AAManagedSection { // Header Row -public class AAHeaderRow: AAManagedRow { +open class AAHeaderRow: AAManagedRow { - public var height: CGFloat = 40 - public var title: String? - public var icon: UIImage? + open var height: CGFloat = 40 + open var title: String? + open var icon: UIImage? - public override func rangeCellHeightForItem(table: AAManagedTable, indexPath: AARangeIndexPath) -> CGFloat { + open override func rangeCellHeightForItem(_ table: AAManagedTable, indexPath: AARangeIndexPath) -> CGFloat { return height } - public override func rangeCellForItem(table: AAManagedTable, indexPath: AARangeIndexPath) -> UITableViewCell { + open override func rangeCellForItem(_ table: AAManagedTable, indexPath: AARangeIndexPath) -> UITableViewCell { let res = table.dequeueCell(indexPath.indexPath) as AAHeaderCell if title == nil { - res.titleView.hidden = true + res.titleView.isHidden = true } else { res.titleView.text = title - res.titleView.hidden = false + res.titleView.isHidden = false } if icon == nil { - res.iconView.hidden = true + res.iconView.isHidden = true } else { - res.iconView.hidden = false + res.iconView.isHidden = false res.iconView.image = icon!.tintImage(ActorSDK.sharedActor().style.cellHeaderColor) } @@ -293,7 +293,7 @@ public class AAHeaderRow: AAManagedRow { public extension AAManagedSection { - private func header() -> AAHeaderRow { + fileprivate func header() -> AAHeaderRow { let r = AAHeaderRow() regions.append(r) r.section = self @@ -301,7 +301,7 @@ public extension AAManagedSection { return r } - public func header(title: String) -> AAHeaderRow { + public func header(_ title: String) -> AAHeaderRow { let r = header() r.title = title r.initTable(self.table) @@ -312,31 +312,31 @@ public extension AAManagedSection { // Common Row -public class AACommonRow: AAManagedRow { +open class AACommonRow: AAManagedRow { - public var style: AACommonCellStyle = .Normal - public var hint: String? - public var content: String? - public var switchOn: Bool = false - public var switchAction: ((v: Bool) -> ())? + open var style: AACommonCellStyle = .normal + open var hint: String? + open var content: String? + open var switchOn: Bool = false + open var switchAction: ((_ v: Bool) -> ())? - public var bindAction: ((r: AACommonRow)->())? + open var bindAction: ((_ r: AACommonRow)->())? - public var contentInset: CGFloat = 15 + open var contentInset: CGFloat = 15 // Cell - public override func rangeCellHeightForItem(table: AAManagedTable, indexPath: AARangeIndexPath) -> CGFloat { + open override func rangeCellHeightForItem(_ table: AAManagedTable, indexPath: AARangeIndexPath) -> CGFloat { return 44 } - public override func rangeCellForItem(table: AAManagedTable, indexPath: AARangeIndexPath) -> UITableViewCell { + open override func rangeCellForItem(_ table: AAManagedTable, indexPath: AARangeIndexPath) -> UITableViewCell { let res = table.dequeueCommonCell(indexPath.indexPath) bindCell(res) return res } - public func bindCell(res: AACommonCell) { + open func bindCell(_ res: AACommonCell) { res.style = style res.setContent(content) res.setHint(hint) @@ -345,31 +345,31 @@ public class AACommonRow: AAManagedRow { res.contentInset = contentInset if selectAction != nil { - res.selectionStyle = .Default + res.selectionStyle = .default } else { - res.selectionStyle = .None + res.selectionStyle = .none } } // Binding - public func rangeBind(table: AAManagedTable, binder: AABinder) { - bindAction?(r: self) + open func rangeBind(_ table: AAManagedTable, binder: AABinder) { + bindAction?(self) } - public override func reload() { + open override func reload() { - bindAction?(r: self) + bindAction?(self) if let p = indexPath, let s = section { - if let cell = s.table.tableView.cellForRowAtIndexPath(p) as? AACommonCell { + if let cell = s.table.tableView.cellForRow(at: p) as? AACommonCell { bindCell(cell) } } } - public func rebind() { - bindAction?(r: self) + open func rebind() { + bindAction?(self) } } @@ -377,100 +377,100 @@ public extension AAManagedSection { // Common cell - private func common() -> AACommonRow { + fileprivate func common() -> AACommonRow { let r = AACommonRow() let itemsCount = numberOfItems(table) regions.append(r) - r.indexPath = NSIndexPath(forRow: itemsCount, inSection: index) + r.indexPath = IndexPath(row: itemsCount, section: index) r.section = self r.initTable(self.table) return r } - public func common(@noescape closure: (r: AACommonRow) -> ()) -> AACommonRow { + public func common(_ closure: (_ r: AACommonRow) -> ()) -> AACommonRow { let r = common() - closure(r: r) - r.bindAction?(r: r) + closure(r) + r.bindAction?(r) r.initTable(self.table) return r } // Action cell - public func action(@noescape closure: (r: AACommonRow) -> ()) -> AACommonRow { + public func action(_ closure: (_ r: AACommonRow) -> ()) -> AACommonRow { let r = common() - r.style = .Action - closure(r: r) - r.bindAction?(r: r) + r.style = .action + closure(r) + r.bindAction?(r) r.initTable(self.table) return r } - public func action(content: String, @noescape closure: (r: AACommonRow) -> ()) -> AACommonRow { + public func action(_ content: String, closure: (_ r: AACommonRow) -> ()) -> AACommonRow { let r = common() r.content = AALocalized(content) - r.style = .Action - closure(r: r) - r.bindAction?(r: r) + r.style = .action + closure(r) + r.bindAction?(r) r.initTable(self.table) return r } // Navigation cell - public func navigate(@noescape closure: (r: AACommonRow) -> ()) -> AACommonRow { + public func navigate(_ closure: (_ r: AACommonRow) -> ()) -> AACommonRow { let r = common() - r.style = .Navigation - closure(r: r) - r.bindAction?(r: r) + r.style = .navigation + closure(r) + r.bindAction?(r) r.initTable(self.table) return r } - public func navigate(content: String, @noescape closure: (r: AACommonRow) -> ()) -> AACommonRow { + public func navigate(_ content: String, closure: (_ r: AACommonRow) -> ()) -> AACommonRow { let r = common() r.content = AALocalized(content) - r.style = .Navigation - closure(r: r) - r.bindAction?(r: r) + r.style = .navigation + closure(r) + r.bindAction?(r) r.initTable(self.table) return r } - public func navigate(content: String, controller: UIViewController.Type) -> AACommonRow { + public func navigate(_ content: String, controller: UIViewController.Type) -> AACommonRow { let r = common() r.content = AALocalized(content) - r.style = .Navigation + r.style = .navigation r.selectAction = { () -> Bool in self.table.controller.navigateNext(controller.init()) return false } - r.bindAction?(r: r) + r.bindAction?(r) r.initTable(self.table) return r } // Danger - public func danger(content: String, @noescape closure: (r: AACommonRow) -> ()) -> AACommonRow { + public func danger(_ content: String, closure: (_ r: AACommonRow) -> ()) -> AACommonRow { let r = common() r.content = AALocalized(content) - r.style = .Destructive - closure(r: r) - r.bindAction?(r: r) + r.style = .destructive + closure(r) + r.bindAction?(r) r.initTable(self.table) return r } // Content - func url(content: String, url: String) -> AACommonRow { + func url(_ content: String, url: String) -> AACommonRow { let r = common() r.content = AALocalized(content) - r.style = .Navigation + r.style = .navigation r.selectAction = { () -> Bool in - if let u = NSURL(string: url) { - UIApplication.sharedApplication().openURL(u) + if let u = URL(string: url) { + UIApplication.shared.openURL(u) } return true } @@ -478,11 +478,11 @@ public extension AAManagedSection { return r } - public func hint(content: String) -> AACommonRow { + public func hint(_ content: String) -> AACommonRow { let r = common() r.content = AALocalized(content) - r.style = .Hint - r.bindAction?(r: r) + r.style = .hint + r.bindAction?(r) r.initTable(self.table) return r } @@ -491,34 +491,34 @@ public extension AAManagedSection { // Custom cell -public class AACustomRow: AAManagedRow { +open class AACustomRow: AAManagedRow where T: UITableViewCell { - public var height: CGFloat = 44 - public var closure: ((cell: T) -> ())? + open var height: CGFloat = 44 + open var closure: ((_ cell: T) -> ())? - public var bindAction: ((r: AACustomRow)->())? + open var bindAction: ((_ r: AACustomRow)->())? // Cell - public override func rangeCellHeightForItem(table: AAManagedTable, indexPath: AARangeIndexPath) -> CGFloat { + open override func rangeCellHeightForItem(_ table: AAManagedTable, indexPath: AARangeIndexPath) -> CGFloat { return height } - public override func rangeCellForItem(table: AAManagedTable, indexPath: AARangeIndexPath) -> UITableViewCell { + open override func rangeCellForItem(_ table: AAManagedTable, indexPath: AARangeIndexPath) -> UITableViewCell { let res: T = table.dequeueCell(indexPath.indexPath) rangeBindCellForItem(table, indexPath: indexPath, cell: res) return res } - public func rangeBindCellForItem(table: AAManagedTable, indexPath: AARangeIndexPath, cell: T) { - closure?(cell: cell) + open func rangeBindCellForItem(_ table: AAManagedTable, indexPath: AARangeIndexPath, cell: T) { + closure?(cell) } // Binding - public override func reload() { + open override func reload() { - bindAction?(r: self) + bindAction?(self) super.reload() } @@ -526,20 +526,20 @@ public class AACustomRow: AAManagedRow { public extension AAManagedSection { - private func custom() -> AACustomRow { + fileprivate func custom() -> AACustomRow where T: UITableViewCell { let r = AACustomRow() let itemsCount = numberOfItems(table) regions.append(r) - r.indexPath = NSIndexPath(forRow: itemsCount, inSection: index) + r.indexPath = IndexPath(row: itemsCount, section: index) r.section = self r.initTable(self.table) return r } - public func custom(@noescape closure: (r: AACustomRow) -> ()) -> AACustomRow { + public func custom(_ closure: (_ r: AACustomRow) -> ()) -> AACustomRow where T: UITableViewCell { let r: AACustomRow = custom() - closure(r: r) - r.bindAction?(r: r) + closure(r) + r.bindAction?(r) r.initTable(self.table) return r } @@ -547,39 +547,39 @@ public extension AAManagedSection { // Avatar Row -public class AAAvatarRow: AAManagedRow { +open class AAAvatarRow: AAManagedRow { - public var id: Int? - public var title: String? + open var id: Int? + open var title: String? - public var avatar: ACAvatar? - public var avatarPath: String? - public var avatarLoading: Bool = false + open var avatar: ACAvatar? + open var avatarPath: String? + open var avatarLoading: Bool = false - public var subtitleHidden: Bool = false - public var subtitle: String? - public var subtitleColor: UIColor? + open var subtitleHidden: Bool = false + open var subtitle: String? + open var subtitleColor: UIColor? - public var bindAction: ((r: AAAvatarRow)->())? + open var bindAction: ((_ r: AAAvatarRow)->())? - public var avatarDidTap: ((view: UIView) -> ())? + open var avatarDidTap: ((_ view: UIView) -> ())? // Cell - public override func rangeCellHeightForItem(table: AAManagedTable, indexPath: AARangeIndexPath) -> CGFloat { + open override func rangeCellHeightForItem(_ table: AAManagedTable, indexPath: AARangeIndexPath) -> CGFloat { return 92 } - public override func rangeCellForItem(table: AAManagedTable, indexPath: AARangeIndexPath) -> UITableViewCell { + open override func rangeCellForItem(_ table: AAManagedTable, indexPath: AARangeIndexPath) -> UITableViewCell { let res: AAAvatarCell = table.dequeueCell(indexPath.indexPath) bindCell(res) return res } - public func bindCell(res: AAAvatarCell) { + open func bindCell(_ res: AAAvatarCell) { res.titleLabel.text = title - res.subtitleLabel.hidden = subtitleHidden + res.subtitleLabel.isHidden = subtitleHidden res.subtitleLabel.text = subtitle if avatarPath != nil { @@ -594,7 +594,7 @@ public class AAAvatarRow: AAManagedRow { res.subtitleLabel.textColor = ActorSDK.sharedActor().style.cellTextColor } - res.progress.hidden = !avatarLoading + res.progress.isHidden = !avatarLoading if avatarLoading { res.progress.startAnimating() } else { @@ -606,9 +606,9 @@ public class AAAvatarRow: AAManagedRow { // Binding - public override func reload() { + open override func reload() { - bindAction?(r: self) + bindAction?(self) if let p = indexPath, let s = section { if let cell = s.table.tableView.visibleCellForIndexPath(p) as? AAAvatarCell { @@ -620,20 +620,20 @@ public class AAAvatarRow: AAManagedRow { public extension AAManagedSection { - private func addAvatar() -> AAAvatarRow { + fileprivate func addAvatar() -> AAAvatarRow { let r = AAAvatarRow() let itemsCount = numberOfItems(table) regions.append(r) - r.indexPath = NSIndexPath(forRow: itemsCount, inSection: index) + r.indexPath = IndexPath(row: itemsCount, section: index) r.section = self r.initTable(self.table) return r } - public func avatar(@noescape closure: (r: AAAvatarRow) -> ()) -> AAAvatarRow { + public func avatar(_ closure: (_ r: AAAvatarRow) -> ()) -> AAAvatarRow { let r: AAAvatarRow = addAvatar() - closure(r: r) - r.bindAction?(r: r) + closure(r) + r.bindAction?(r) r.initTable(self.table) return r } @@ -641,88 +641,88 @@ public extension AAManagedSection { // Arrays -public class AAManagedArrayRows: AAManagedRange { +open class AAManagedArrayRows: AAManagedRange where R: UITableViewCell { - public var height: CGFloat = 44 - public var selectAction: ((t: T) -> Bool)? - public var bindCopy: ((t: T) -> String?)? - public var section: AAManagedSection? + open var height: CGFloat = 44 + open var selectAction: ((_ t: T) -> Bool)? + open var bindCopy: ((_ t: T) -> String?)? + open var section: AAManagedSection? - public var bindData: ((cell: R, item: T) -> ())? + open var bindData: ((_ cell: R, _ item: T) -> ())? - public var itemShown: ((index: Int, item: T) -> ())? + open var itemShown: ((_ index: Int, _ item: T) -> ())? - public var data = [T]() + open var data = [T]() - public func initTable(table: AAManagedTable) { + open func initTable(_ table: AAManagedTable) { } // Number of items - public func rangeNumberOfItems(table: AAManagedTable) -> Int { + open func rangeNumberOfItems(_ table: AAManagedTable) -> Int { return data.count } // Cells - public func rangeCellHeightForItem(table: AAManagedTable, indexPath: AARangeIndexPath) -> CGFloat { + open func rangeCellHeightForItem(_ table: AAManagedTable, indexPath: AARangeIndexPath) -> CGFloat { return height } - public func rangeCellForItem(table: AAManagedTable, indexPath: AARangeIndexPath) -> UITableViewCell { + open func rangeCellForItem(_ table: AAManagedTable, indexPath: AARangeIndexPath) -> UITableViewCell { let res: R = table.dequeueCell(indexPath.indexPath) let item = data[indexPath.item] rangeBindData(table, indexPath: indexPath, cell: res, item: item) if let shown = itemShown { - shown(index: indexPath.item, item: item) + shown(indexPath.item, item) } return res } - public func rangeBindData(table: AAManagedTable, indexPath: AARangeIndexPath, cell: R, item: T) { - bindData?(cell: cell, item: item) + open func rangeBindData(_ table: AAManagedTable, indexPath: AARangeIndexPath, cell: R, item: T) { + bindData?(cell, item) } // Selection - public func rangeCanSelect(table: AAManagedTable, indexPath: AARangeIndexPath) -> Bool { + open func rangeCanSelect(_ table: AAManagedTable, indexPath: AARangeIndexPath) -> Bool { return selectAction != nil } - public func rangeSelect(table: AAManagedTable, indexPath: AARangeIndexPath) -> Bool { - return selectAction!(t: data[indexPath.item]) + open func rangeSelect(_ table: AAManagedTable, indexPath: AARangeIndexPath) -> Bool { + return selectAction!(data[indexPath.item]) } // Copy - public func rangeCanCopy(table: AAManagedTable, indexPath: AARangeIndexPath) -> Bool { + open func rangeCanCopy(_ table: AAManagedTable, indexPath: AARangeIndexPath) -> Bool { if bindCopy != nil { - return bindCopy!(t: data[indexPath.item]) != nil + return bindCopy!(data[indexPath.item]) != nil } return false } - public func rangeCopy(table: AAManagedTable, indexPath: AARangeIndexPath) { - if let s = bindCopy!(t: data[indexPath.item]) { - UIPasteboard.generalPasteboard().string = s + open func rangeCopy(_ table: AAManagedTable, indexPath: AARangeIndexPath) { + if let s = bindCopy!(data[indexPath.item]) { + UIPasteboard.general.string = s } } // Reloading - public func reload() { + open func reload() { if let s = section { - s.table.tableView.reloadSections(NSIndexSet(index: s.index), withRowAnimation: .Automatic) + s.table.tableView.reloadSections(IndexSet(integer: s.index), with: .automatic) } } } public extension AAManagedSection { - private func addArrays() -> AAManagedArrayRows { + fileprivate func addArrays() -> AAManagedArrayRows where R: UITableViewCell { let r = AAManagedArrayRows() regions.append(r) r.section = self @@ -730,9 +730,9 @@ public extension AAManagedSection { return r } - public func arrays(@noescape closure: (r: AAManagedArrayRows) -> ()) -> AAManagedArrayRows { + public func arrays(_ closure: (_ r: AAManagedArrayRows) -> ()) -> AAManagedArrayRows where R: UITableViewCell { let res: AAManagedArrayRows = addArrays() - closure(r: res) + closure(res) res.initTable(self.table) return res } @@ -741,60 +741,60 @@ public extension AAManagedSection { // Single item row -public class AAManagedRow: NSObject, AAManagedRange { +open class AAManagedRow: NSObject, AAManagedRange { - public var selectAction: (() -> Bool)? + open var selectAction: (() -> Bool)? - public var section: AAManagedSection? - public var indexPath: NSIndexPath? + open var section: AAManagedSection? + open var indexPath: IndexPath? - public func initTable(table: AAManagedTable) { + open func initTable(_ table: AAManagedTable) { } // Number of items - public func rangeNumberOfItems(table: AAManagedTable) -> Int { + open func rangeNumberOfItems(_ table: AAManagedTable) -> Int { return 1 } // Cells - public func rangeCellHeightForItem(table: AAManagedTable, indexPath: AARangeIndexPath) -> CGFloat { + open func rangeCellHeightForItem(_ table: AAManagedTable, indexPath: AARangeIndexPath) -> CGFloat { return 44 } - public func rangeCellForItem(table: AAManagedTable, indexPath: AARangeIndexPath) -> UITableViewCell { + open func rangeCellForItem(_ table: AAManagedTable, indexPath: AARangeIndexPath) -> UITableViewCell { fatalError("Not implemented") } // Selection - public func rangeCanSelect(table: AAManagedTable, indexPath: AARangeIndexPath) -> Bool { + open func rangeCanSelect(_ table: AAManagedTable, indexPath: AARangeIndexPath) -> Bool { return selectAction != nil } - public func rangeSelect(table: AAManagedTable, indexPath: AARangeIndexPath) -> Bool { + open func rangeSelect(_ table: AAManagedTable, indexPath: AARangeIndexPath) -> Bool { return selectAction!() } // Copying - public func rangeCanCopy(table: AAManagedTable, indexPath: AARangeIndexPath) -> Bool { + open func rangeCanCopy(_ table: AAManagedTable, indexPath: AARangeIndexPath) -> Bool { return rangeCopyData(table, indexPath: indexPath) != nil } - public func rangeCopy(table: AAManagedTable, indexPath: AARangeIndexPath) { - UIPasteboard.generalPasteboard().string = rangeCopyData(table, indexPath: indexPath) + open func rangeCopy(_ table: AAManagedTable, indexPath: AARangeIndexPath) { + UIPasteboard.general.string = rangeCopyData(table, indexPath: indexPath) } - public func rangeCopyData(table: AAManagedTable, indexPath: AARangeIndexPath) -> String? { + open func rangeCopyData(_ table: AAManagedTable, indexPath: AARangeIndexPath) -> String? { return nil } - public func reload() { + open func reload() { if let p = indexPath, let s = section { - s.table.tableView.reloadRowsAtIndexPaths([p], withRowAnimation: .Automatic) + s.table.tableView.reloadRows(at: [p], with: .automatic) } } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/ManagedTableExtensions.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/ManagedTableExtensions.swift index 2aa8ab8ac0..619003edd8 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/ManagedTableExtensions.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/ManagedTableExtensions.swift @@ -6,22 +6,22 @@ import Foundation public extension AAManagedTable { - public func dequeueCell(indexPath: NSIndexPath) -> T { + public func dequeueCell(_ indexPath: IndexPath) -> T where T: UITableViewCell { return self.tableView.dequeueCell(indexPath) } } public extension AAManagedTable { - public func dequeueTextCell(indexPath: NSIndexPath) -> AATextCell { + public func dequeueTextCell(_ indexPath: IndexPath) -> AATextCell { return dequeueCell(indexPath) } - public func dequeueTitledCell(indexPath: NSIndexPath) -> AATitledCell { + public func dequeueTitledCell(_ indexPath: IndexPath) -> AATitledCell { return dequeueCell(indexPath) } - public func dequeueCommonCell(indexPath: NSIndexPath) -> AACommonCell { + public func dequeueCommonCell(_ indexPath: IndexPath) -> AACommonCell { return dequeueCell(indexPath) } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/Navigations.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/Navigations.swift index 6003e7955e..88c83b56e0 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/Navigations.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/Navigations.swift @@ -5,15 +5,15 @@ import Foundation public extension UIViewController { - public func navigateDetail(controller: UIViewController) { + public func navigateDetail(_ controller: UIViewController) { if (AADevice.isiPad) { - let split = UIApplication.sharedApplication().keyWindow?.rootViewController as! UISplitViewController + let split = UIApplication.shared.keyWindow?.rootViewController as! UISplitViewController let master = split.viewControllers[0] let detail = AANavigationController() detail.viewControllers = [controller] split.viewControllers = [master, detail] } else { - let tabBar = UIApplication.sharedApplication().keyWindow?.rootViewController as! UITabBarController + let tabBar = UIApplication.shared.keyWindow?.rootViewController as! UITabBarController controller.hidesBottomBarWhenPushed = true (tabBar.selectedViewController as! AANavigationController).pushViewController(controller, animated: true) } @@ -21,7 +21,7 @@ public extension UIViewController { } public extension UIViewController { - public func navigateNext(controller: UIViewController, removeCurrent: Bool = false) { + public func navigateNext(_ controller: UIViewController, removeCurrent: Bool = false) { if let aaC = controller as? AAViewController, let aaSelf = self as? AAViewController { aaC.popover = aaSelf.popover } @@ -45,7 +45,7 @@ public extension UIViewController { public func navigateBack() { if (self.navigationController!.viewControllers.last != nil) { if (self.navigationController!.viewControllers.last! == self) { - self.navigationController!.popViewControllerAnimated(true) + self.navigationController!.popViewController(animated: true) } else { } @@ -60,4 +60,4 @@ public extension UIViewController { self.navigationController!.setViewControllers(nControllers, animated: true); } } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Recent/AARecentViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Recent/AARecentViewController.swift index 9f9df5a34a..2138b077d4 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Recent/AARecentViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Recent/AARecentViewController.swift @@ -4,9 +4,9 @@ import UIKit -public class AARecentViewController: AADialogsListContentController, AADialogsListContentControllerDelegate { +open class AARecentViewController: AADialogsListContentController, AADialogsListContentControllerDelegate { - private var isBinded = true + fileprivate var isBinded = true public override init() { @@ -14,7 +14,7 @@ public class AARecentViewController: AADialogsListContentController, AADialogsLi // Enabling dialogs page tracking - content = ACAllEvents_Main.RECENT() + content = ACAllEvents_Main.recent() // Setting delegate @@ -27,10 +27,10 @@ public class AARecentViewController: AADialogsListContentController, AADialogsLi // Setting navigation item navigationItem.title = AALocalized("TabMessages") - navigationItem.leftBarButtonItem = editButtonItem() + navigationItem.leftBarButtonItem = editButtonItem navigationItem.leftBarButtonItem!.title = AALocalized("NavigationEdit") - navigationItem.backBarButtonItem = UIBarButtonItem(title: AALocalized("DialogsBack"), style: UIBarButtonItemStyle.Plain, target: nil, action: nil) - navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.Compose, target: self, action: #selector(AARecentViewController.compose)) + navigationItem.backBarButtonItem = UIBarButtonItem(title: AALocalized("DialogsBack"), style: UIBarButtonItemStyle.plain, target: nil, action: nil) + navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.compose, target: self, action: #selector(AARecentViewController.compose)) bindCounter() } @@ -41,30 +41,30 @@ public class AARecentViewController: AADialogsListContentController, AADialogsLi // Implemention of editing - public override func setEditing(editing: Bool, animated: Bool) { + open override func setEditing(_ editing: Bool, animated: Bool) { super.setEditing(editing, animated: animated) tableView.setEditing(editing, animated: animated) if (editing) { self.navigationItem.leftBarButtonItem!.title = AALocalized("NavigationDone") - self.navigationItem.leftBarButtonItem!.style = UIBarButtonItemStyle.Done + self.navigationItem.leftBarButtonItem!.style = UIBarButtonItemStyle.done navigationItem.rightBarButtonItem = nil } else { self.navigationItem.leftBarButtonItem!.title = AALocalized("NavigationEdit") - self.navigationItem.leftBarButtonItem!.style = UIBarButtonItemStyle.Plain + self.navigationItem.leftBarButtonItem!.style = UIBarButtonItemStyle.plain - navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.Compose, target: self, action: #selector(AARecentViewController.compose)) + navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.compose, target: self, action: #selector(AARecentViewController.compose)) } if editing == true { navigationItem.rightBarButtonItem = nil } else { - navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.Compose, target: self, action: #selector(AARecentViewController.compose)) + navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.compose, target: self, action: #selector(AARecentViewController.compose)) } } - public func compose() { + open func compose() { if AADevice.isiPad { self.presentElegantViewController(AANavigationController(rootViewController: AAComposeController())) } else { @@ -74,7 +74,7 @@ public class AARecentViewController: AADialogsListContentController, AADialogsLi // Tracking app state - public override func viewWillAppear(animated: Bool) { + open override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) bindCounter() } @@ -84,8 +84,8 @@ public class AARecentViewController: AADialogsListContentController, AADialogsLi isBinded = true binder.bind(Actor.getGlobalState().globalCounter, closure: { (value: JavaLangInteger?) -> () in if value != nil { - if value!.integerValue > 0 { - self.tabBarItem.badgeValue = "\(value!.integerValue)" + if value!.intValue > 0 { + self.tabBarItem.badgeValue = "\(value!.intValue)" } else { self.tabBarItem.badgeValue = nil } @@ -98,24 +98,24 @@ public class AARecentViewController: AADialogsListContentController, AADialogsLi } - public override func viewWillDisappear(animated: Bool) { + open override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) isBinded = false } - public override func viewDidAppear(animated: Bool) { + open override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) Actor.onDialogsOpen() } - public override func viewDidDisappear(animated: Bool) { + open override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) Actor.onDialogsClosed() } // Handling selections - public func recentsDidTap(controller: AADialogsListContentController, dialog: ACDialog) -> Bool { + open func recentsDidTap(_ controller: AADialogsListContentController, dialog: ACDialog) -> Bool { if let customController = ActorSDK.sharedActor().delegate.actorControllerForConversation(dialog.peer) { self.navigateDetail(customController) } else { @@ -124,7 +124,7 @@ public class AARecentViewController: AADialogsListContentController, AADialogsLi return false } - public func searchDidTap(controller: AADialogsListContentController, entity: ACSearchResult) { + open func searchDidTap(_ controller: AADialogsListContentController, entity: ACSearchResult) { if let customController = ActorSDK.sharedActor().delegate.actorControllerForConversation(entity.peer) { self.navigateDetail(customController) } else { diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Ringtones/AARingtonesViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Ringtones/AARingtonesViewController.swift index 121fe920d3..ecbdaef3f2 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Ringtones/AARingtonesViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Ringtones/AARingtonesViewController.swift @@ -10,7 +10,7 @@ import Foundation import UIKit import AVFoundation -public class AARingtonesViewController: AATableViewController { +open class AARingtonesViewController: AATableViewController { var audioPlayer: AVAudioPlayer! var selectedRingtone: String = "" @@ -21,21 +21,21 @@ public class AARingtonesViewController: AATableViewController { var soundFiles: [(directory: String, files: [String])] = [] init() { - super.init(style: UITableViewStyle.Plain) + super.init(style: UITableViewStyle.plain) self.title = AALocalized("Ringtones") - let cancelButtonItem = UIBarButtonItem(title: AALocalized("NavigationCancel"), style: UIBarButtonItemStyle.Plain, target: self, action: Selector("dismiss")) - let doneButtonItem = UIBarButtonItem(title: AALocalized("NavigationDone"), style: UIBarButtonItemStyle.Plain, target: self, action: Selector("dismiss")) - self.navigationItem.setLeftBarButtonItem(cancelButtonItem, animated: false) - self.navigationItem.setRightBarButtonItem(doneButtonItem, animated: false) + let cancelButtonItem = UIBarButtonItem(title: AALocalized("NavigationCancel"), style: UIBarButtonItemStyle.plain, target: self, action: Selector("dismiss")) + let doneButtonItem = UIBarButtonItem(title: AALocalized("NavigationDone"), style: UIBarButtonItemStyle.plain, target: self, action: Selector("dismiss")) + self.navigationItem.setLeftBarButton(cancelButtonItem, animated: false) + self.navigationItem.setRightBarButton(doneButtonItem, animated: false) } required public init(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - override public func viewDidLoad() { + override open func viewDidLoad() { super.viewDidLoad() for directory in rootSoundDirectories { directories.append(directory) @@ -45,38 +45,38 @@ public class AARingtonesViewController: AATableViewController { } getDirectories() - loadSoundFiles() + // loadSoundFiles() tableView.rowHeight = 44.0 - tableView.sectionIndexBackgroundColor = UIColor.clearColor() + tableView.sectionIndexBackgroundColor = UIColor.clear } - override public func viewWillDisappear(animated: Bool) { + override open func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(true) - if(audioPlayer != nil && audioPlayer.playing){ + if(audioPlayer != nil && audioPlayer.isPlaying){ audioPlayer.stop() } } - public override func viewDidDisappear(animated: Bool) { + open override func viewDidDisappear(_ animated: Bool) { completion(selectedRingtone) } - override public func didReceiveMemoryWarning() { + override open func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() } func getDirectories() { - let fileManager: NSFileManager = NSFileManager() + let fileManager: FileManager = FileManager() for directory in rootSoundDirectories { - let directoryURL: NSURL = NSURL(fileURLWithPath: "\(directory)", isDirectory: true) + let directoryURL: URL = URL(fileURLWithPath: "\(directory)", isDirectory: true) do { - if let URLs: [NSURL] = try fileManager.contentsOfDirectoryAtURL(directoryURL, includingPropertiesForKeys: [NSURLIsDirectoryKey], options: NSDirectoryEnumerationOptions()) { + if let URLs: [URL] = try fileManager.contentsOfDirectory(at: directoryURL, includingPropertiesForKeys: [URLResourceKey.isDirectoryKey], options: FileManager.DirectoryEnumerationOptions()) { var urlIsaDirectory: ObjCBool = ObjCBool(false) for url in URLs { - if fileManager.fileExistsAtPath(url.path!, isDirectory: &urlIsaDirectory) { - if urlIsaDirectory { - let directory: String = "\(url.relativePath!)" + if fileManager.fileExists(atPath: url.path, isDirectory: &urlIsaDirectory) { + if urlIsaDirectory.boolValue { + let directory: String = "\(url.relativePath)" let files: [String] = [] let newSoundFile: (directory: String, files: [String]) = (directory, files) directories.append("\(directory)") @@ -91,79 +91,79 @@ public class AARingtonesViewController: AATableViewController { } } - func loadSoundFiles() { - - for i in 0...directories.count-1 { - let fileManager: NSFileManager = NSFileManager() - let directoryURL: NSURL = NSURL(fileURLWithPath: directories[i], isDirectory: true) - - do { - if let URLs: [NSURL] = try fileManager.contentsOfDirectoryAtURL(directoryURL, includingPropertiesForKeys: [NSURLIsDirectoryKey], options: NSDirectoryEnumerationOptions()) { - var urlIsaDirectory: ObjCBool = ObjCBool(false) - for url in URLs { - if fileManager.fileExistsAtPath(url.path!, isDirectory: &urlIsaDirectory) { - if !urlIsaDirectory { - soundFiles[i].files.append("\(url.lastPathComponent!)") - } - } - } - } - } catch { - debugPrint("\(error)") - } - } - } +// func loadSoundFiles() { + +// for i in 0...directories.count-1 { +// let fileManager: FileManager = FileManager() +// let directoryURL: URL = URL(fileURLWithPath: directories[i], isDirectory: true) +// +// do { +// if let URLs: [URL] = try fileManager.contentsOfDirectory(at: directoryURL, includingPropertiesForKeys: [URLResourceKey.isDirectoryKey], options: FileManager.DirectoryEnumerationOptions()) { +// var urlIsaDirectory: ObjCBool = ObjCBool(false) +// for url in URLs { +// if fileManager.fileExists(atPath: url.path, isDirectory: &urlIsaDirectory) { +// if !urlIsaDirectory { +// soundFiles[i].files.append("\(url.lastPathComponent)") +// } +// } +// } +// } +// } catch { +// debugPrint("\(error)") +// } +// } +// } - override public func numberOfSectionsInTableView(tableView: UITableView) -> Int { - return 1 - } - - override public func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return soundFiles[section].files.count - } - - func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? { +// override open func numberOfSections(in tableView: UITableView) -> Int { +// return 1 +// } +// +// override open func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { +// return soundFiles[section].files.count +// } + + func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { return "Ringtones" } - func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { + func tableView(_ tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: IndexPath) -> CGFloat { return 44 } - func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { + func tableView(_ tableView: UITableView, heightForRowAtIndexPath indexPath: IndexPath) -> CGFloat { return UITableViewAutomaticDimension } - override public func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { - - let fileName: String = soundFiles[indexPath.section].files[indexPath.row] - let cell: AACommonCell = tableView.dequeueCell(indexPath) - cell.style = .Normal - let name = fileName.componentsSeparatedByString(".m4r") - cell.textLabel?.text = name.first - return cell - } +// override open func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { +// +// let fileName: String = soundFiles[(indexPath as NSIndexPath).section].files[(indexPath as NSIndexPath).row] +// let cell: AACommonCell = tableView.dequeueCell(indexPath) +// cell.style = .normal +// let name = fileName.components(separatedBy: ".m4r") +// cell.textLabel?.text = name.first +// return cell +// } - func tableView(tableView: UITableView, didDeselectRowAtIndexPath indexPath: NSIndexPath) { - if let cell = tableView.cellForRowAtIndexPath(indexPath) as? AACommonCell { - cell.style = .Normal + func tableView(_ tableView: UITableView, didDeselectRowAtIndexPath indexPath: IndexPath) { + if let cell = tableView.cellForRow(at: indexPath) as? AACommonCell { + cell.style = .normal } } - func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { + func tableView(_ tableView: UITableView, didSelectRowAtIndexPath indexPath: IndexPath) { - let directory: String = soundFiles[indexPath.section].directory - let fileName: String = soundFiles[indexPath.section].files[indexPath.row] - let fileURL: NSURL = NSURL(fileURLWithPath: "\(directory)/\(fileName)") + let directory: String = soundFiles[(indexPath as NSIndexPath).section].directory + let fileName: String = soundFiles[(indexPath as NSIndexPath).section].files[(indexPath as NSIndexPath).row] + let fileURL: URL = URL(fileURLWithPath: "\(directory)/\(fileName)") do { - audioPlayer = try AVAudioPlayer(contentsOfURL: fileURL) + audioPlayer = try AVAudioPlayer(contentsOf: fileURL) audioPlayer.play() } catch { debugPrint("\(error)") selectedRingtone = "" } - let cell = tableView.cellForRowAtIndexPath(indexPath) as! AACommonCell - selectedRingtone = soundFiles[indexPath.section].files[indexPath.row] - cell.style = .Checkmark + let cell = tableView.cellForRow(at: indexPath) as! AACommonCell + selectedRingtone = soundFiles[(indexPath as NSIndexPath).section].files[(indexPath as NSIndexPath).row] + cell.style = .checkmark } } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Root/AANoSelectionViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Root/AANoSelectionViewController.swift index 73bd7f4bb3..f67c9149be 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Root/AANoSelectionViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Root/AANoSelectionViewController.swift @@ -4,7 +4,7 @@ import Foundation -public class AANoSelectionViewController: AAViewController { +open class AANoSelectionViewController: AAViewController { public override init() { super.init(nibName: nil, bundle: nil) @@ -15,4 +15,4 @@ public class AANoSelectionViewController: AAViewController { public required init(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Root/AARootSplitViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Root/AARootSplitViewController.swift index b71e8d59e5..202d5271e9 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Root/AARootSplitViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Root/AARootSplitViewController.swift @@ -4,13 +4,13 @@ import Foundation -public class AARootSplitViewController: UISplitViewController { +open class AARootSplitViewController: UISplitViewController { public init() { super.init(nibName: nil, bundle: nil) - preferredDisplayMode = .AllVisible - if (interfaceOrientation == UIInterfaceOrientation.Portrait || interfaceOrientation == UIInterfaceOrientation.PortraitUpsideDown) { + preferredDisplayMode = .allVisible + if (interfaceOrientation == UIInterfaceOrientation.portrait || interfaceOrientation == UIInterfaceOrientation.portraitUpsideDown) { minimumPrimaryColumnWidth = CGFloat(300.0) maximumPrimaryColumnWidth = CGFloat(300.0) } else { @@ -23,10 +23,10 @@ public class AARootSplitViewController: UISplitViewController { fatalError("init(coder:) has not been implemented") } - public override func willRotateToInterfaceOrientation(toInterfaceOrientation: UIInterfaceOrientation, duration: NSTimeInterval) { - super.willRotateToInterfaceOrientation(toInterfaceOrientation, duration: duration) + open override func willRotate(to toInterfaceOrientation: UIInterfaceOrientation, duration: TimeInterval) { + super.willRotate(to: toInterfaceOrientation, duration: duration) - if (toInterfaceOrientation == UIInterfaceOrientation.Portrait || toInterfaceOrientation == UIInterfaceOrientation.PortraitUpsideDown) { + if (toInterfaceOrientation == UIInterfaceOrientation.portrait || toInterfaceOrientation == UIInterfaceOrientation.portraitUpsideDown) { minimumPrimaryColumnWidth = CGFloat(300.0) maximumPrimaryColumnWidth = CGFloat(300.0) } else { @@ -35,7 +35,7 @@ public class AARootSplitViewController: UISplitViewController { } } - public override func preferredStatusBarStyle() -> UIStatusBarStyle { + open override var preferredStatusBarStyle : UIStatusBarStyle { return ActorSDK.sharedActor().style.vcStatusBarStyle } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Root/AARootTabViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Root/AARootTabViewController.swift index dd2c6c6174..2c194378eb 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Root/AARootTabViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Root/AARootTabViewController.swift @@ -5,14 +5,34 @@ import Foundation import UIKit import MessageUI +fileprivate func < (lhs: T?, rhs: T?) -> Bool { + switch (lhs, rhs) { + case let (l?, r?): + return l < r + case (nil, _?): + return true + default: + return false + } +} -public class AARootTabViewController : UITabBarController, MFMessageComposeViewControllerDelegate, UIAlertViewDelegate { +fileprivate func > (lhs: T?, rhs: T?) -> Bool { + switch (lhs, rhs) { + case let (l?, r?): + return l > r + default: + return rhs < lhs + } +} + + +open class AARootTabViewController : UITabBarController, MFMessageComposeViewControllerDelegate, UIAlertViewDelegate { - private let binder = AABinder() + fileprivate let binder = AABinder() - private var appEmptyContainer = UIView() - private var appIsSyncingPlaceholder = AABigPlaceholderView(topOffset: 44 + 20) - private var appIsEmptyPlaceholder = AABigPlaceholderView(topOffset: 44 + 20) + fileprivate var appEmptyContainer = UIView() + fileprivate var appIsSyncingPlaceholder = AABigPlaceholderView(topOffset: 44 + 20) + fileprivate var appIsEmptyPlaceholder = AABigPlaceholderView(topOffset: 44 + 20) public init() { super.init(nibName: nil, bundle: nil) @@ -25,13 +45,13 @@ public class AARootTabViewController : UITabBarController, MFMessageComposeViewC fatalError("Not implemented") } - public override func viewDidLoad() { + open override func viewDidLoad() { super.viewDidLoad() tabBar.barTintColor = ActorSDK.sharedActor().style.tabBgColor - appEmptyContainer.hidden = true - appIsEmptyPlaceholder.hidden = true + appEmptyContainer.isHidden = true + appIsEmptyPlaceholder.isHidden = true appIsEmptyPlaceholder.setImage( UIImage.bundled("contacts_list_placeholder"), title: AALocalized("Placeholder_Empty_Title"), @@ -43,7 +63,7 @@ public class AARootTabViewController : UITabBarController, MFMessageComposeViewC action2Selector: #selector(AARootTabViewController.doAddContact)) appEmptyContainer.addSubview(appIsEmptyPlaceholder) - appIsSyncingPlaceholder.hidden = true + appIsSyncingPlaceholder.isHidden = true appIsSyncingPlaceholder.setImage( UIImage.bundled("chat_list_placeholder"), title: AALocalized("Placeholder_Loading_Title"), @@ -65,23 +85,23 @@ public class AARootTabViewController : UITabBarController, MFMessageComposeViewC } } - public func showAppIsSyncingPlaceholder() { - appIsEmptyPlaceholder.hidden = true - appIsSyncingPlaceholder.hidden = false - appEmptyContainer.hidden = false + open func showAppIsSyncingPlaceholder() { + appIsEmptyPlaceholder.isHidden = true + appIsSyncingPlaceholder.isHidden = false + appEmptyContainer.isHidden = false } - public func showAppIsEmptyPlaceholder() { - appIsEmptyPlaceholder.hidden = false - appIsSyncingPlaceholder.hidden = true - appEmptyContainer.hidden = false + open func showAppIsEmptyPlaceholder() { + appIsEmptyPlaceholder.isHidden = false + appIsSyncingPlaceholder.isHidden = true + appEmptyContainer.isHidden = false } - public func hidePlaceholders() { - appEmptyContainer.hidden = true + open func hidePlaceholders() { + appEmptyContainer.isHidden = true } - public func showSmsInvitation(phone: String?) { + open func showSmsInvitation(_ phone: String?) { if MFMessageComposeViewController.canSendText() { let messageComposeController = MFMessageComposeViewController() messageComposeController.messageComposeDelegate = self @@ -92,7 +112,7 @@ public class AARootTabViewController : UITabBarController, MFMessageComposeViewC .replace("{link}", dest: ActorSDK.sharedActor().inviteUrl) .replace("{appname}", dest: ActorSDK.sharedActor().appName) messageComposeController.navigationBar.tintColor = ActorSDK.sharedActor().style.navigationTitleColor - presentViewController(messageComposeController, animated: true, completion: { () -> Void in + present(messageComposeController, animated: true, completion: { () -> Void in }) } else { @@ -100,11 +120,11 @@ public class AARootTabViewController : UITabBarController, MFMessageComposeViewC } } - public func showSmsInvitation() { + open func showSmsInvitation() { showSmsInvitation(nil) } - public func doAddContact() { + open func doAddContact() { let alertView = UIAlertView( title: AALocalized("ContactsAddHeader"), message: AALocalized("ContactsAddHint"), @@ -112,14 +132,14 @@ public class AARootTabViewController : UITabBarController, MFMessageComposeViewC cancelButtonTitle: AALocalized("AlertCancel"), otherButtonTitles: AALocalized("AlertNext")) - alertView.alertViewStyle = UIAlertViewStyle.PlainTextInput + alertView.alertViewStyle = UIAlertViewStyle.plainTextInput alertView.show() } // MARK: - // MARK: Layout - public override func viewWillLayoutSubviews() { + open override func viewWillLayoutSubviews() { super.viewWillLayoutSubviews() appEmptyContainer.frame = view.bounds @@ -127,41 +147,41 @@ public class AARootTabViewController : UITabBarController, MFMessageComposeViewC appIsEmptyPlaceholder.frame = CGRect(x: 0, y: 0, width: view.bounds.width, height: view.bounds.height) } - public override func shouldAutorotate() -> Bool { + open override var shouldAutorotate : Bool { return false } - public override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask { - return UIInterfaceOrientationMask.Portrait + open override var supportedInterfaceOrientations : UIInterfaceOrientationMask { + return UIInterfaceOrientationMask.portrait } - public override func preferredStatusBarStyle() -> UIStatusBarStyle { + open override var preferredStatusBarStyle : UIStatusBarStyle { return ActorSDK.sharedActor().style.vcStatusBarStyle } - public func messageComposeViewController(controller: MFMessageComposeViewController, didFinishWithResult result: MessageComposeResult) { - controller.dismissViewControllerAnimated(true, completion: nil) + open func messageComposeViewController(_ controller: MFMessageComposeViewController, didFinishWith result: MessageComposeResult) { + controller.dismiss(animated: true, completion: nil) } - public func alertView(alertView: UIAlertView, clickedButtonAtIndex buttonIndex: Int) { + open func alertView(_ alertView: UIAlertView, clickedButtonAt buttonIndex: Int) { // TODO: Localize if buttonIndex == 1 { - let textField = alertView.textFieldAtIndex(0)! + let textField = alertView.textField(at: 0)! if textField.text?.length > 0 { - self.execute(Actor.findUsersCommandWithQuery(textField.text), successBlock: { (val) -> Void in + self.execute(Actor.findUsersCommand(withQuery: textField.text), successBlock: { (val) -> Void in var user: ACUserVM? user = val as? ACUserVM if user == nil { if let users = val as? IOSObjectArray { if Int(users.length()) > 0 { - if let tempUser = users.objectAtIndex(0) as? ACUserVM { + if let tempUser = users.object(at: 0) as? ACUserVM { user = tempUser } } } } if user != nil { - self.execute(Actor.addContactCommandWithUid(user!.getId())!, successBlock: { (val) -> () in + self.execute(Actor.addContactCommand(withUid: user!.getId())!, successBlock: { (val) -> () in // DO Nothing }, failureBlock: { (val) -> () in self.showSmsInvitation(textField.text) @@ -175,4 +195,4 @@ public class AARootTabViewController : UITabBarController, MFMessageComposeViewC } } } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Settings/AASettingsLastSeenController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Settings/AASettingsLastSeenController.swift index 2dbd16a6b6..5f84db7daa 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Settings/AASettingsLastSeenController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Settings/AASettingsLastSeenController.swift @@ -4,21 +4,21 @@ import UIKit -public class AASettingsLastSeenController: AATableViewController { +open class AASettingsLastSeenController: AATableViewController { - private var privacy = Actor.getPrivacy() + fileprivate var privacy = Actor.getPrivacy() // MARK: - // MARK: Constructors - private let CellIdentifier = "CellIdentifier" + fileprivate let CellIdentifier = "CellIdentifier" public init() { - super.init(style: UITableViewStyle.Grouped) + super.init(style: UITableViewStyle.grouped) title = AALocalized("PrivacyLastSeen") - content = ACAllEvents_Settings.NOTIFICATIONS() + content = ACAllEvents_Settings.notifications() } public required init(coder aDecoder: NSCoder) { @@ -27,10 +27,10 @@ public class AASettingsLastSeenController: AATableViewController { // MARK: - - public override func viewDidLoad() { + open override func viewDidLoad() { super.viewDidLoad() - tableView.registerClass(AACommonCell.self, forCellReuseIdentifier: CellIdentifier) + tableView.register(AACommonCell.self, forCellReuseIdentifier: CellIdentifier) tableView.backgroundColor = appStyle.vcBackyardColor tableView.separatorColor = appStyle.vcSeparatorColor @@ -40,58 +40,58 @@ public class AASettingsLastSeenController: AATableViewController { // MARK: - // MARK: UITableView Data Source - public override func numberOfSectionsInTableView(tableView: UITableView) -> Int { + open override func numberOfSections(in tableView: UITableView) -> Int { return 1 } - public override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + open override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 3 } - public func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + open func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { return nil } - public func tableView(tableView: UITableView, titleForFooterInSection section: Int) -> String? { + open func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { return nil } - private func lastSeenCell(indexPath: NSIndexPath) -> AACommonCell { - let cell = tableView.dequeueReusableCellWithIdentifier(CellIdentifier, forIndexPath: indexPath) as! AACommonCell + fileprivate func lastSeenCell(_ indexPath: IndexPath) -> AACommonCell { + let cell = tableView.dequeueReusableCell(withIdentifier: CellIdentifier, for: indexPath) as! AACommonCell - if indexPath.row == 0 { + if (indexPath as NSIndexPath).row == 0 { cell.setContent(AALocalized("PrivacyLastSeenEverybody")) if (self.privacy == "always") { - cell.style = .Checkmark + cell.style = .checkmark } else { - cell.style = .Normal + cell.style = .normal } - } else if indexPath.row == 1 { + } else if (indexPath as NSIndexPath).row == 1 { cell.setContent(AALocalized("PrivacyLastSeenContacts")) if (self.privacy == "contacts") { - cell.style = .Checkmark + cell.style = .checkmark } else { - cell.style = .Normal + cell.style = .normal } - } else if indexPath.row == 2 { + } else if (indexPath as NSIndexPath).row == 2 { cell.setContent(AALocalized("PrivacyLastSeenNone")) if (self.privacy == "none") { - cell.style = .Checkmark + cell.style = .checkmark } else { - cell.style = .Normal + cell.style = .normal } } - cell.selectionStyle = UITableViewCellSelectionStyle.None + cell.selectionStyle = UITableViewCellSelectionStyle.none cell.bottomSeparatorVisible = false cell.topSeparatorVisible = false cell.bottomSeparatorLeftInset = 0 @@ -100,32 +100,32 @@ public class AASettingsLastSeenController: AATableViewController { return cell } - public override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { + open override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { return lastSeenCell(indexPath) } - public func tableView(tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) { + open func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) { let header: UITableViewHeaderFooterView = view as! UITableViewHeaderFooterView header.textLabel!.textColor = ActorSDK.sharedActor().style.cellFooterColor } - public func tableView(tableView: UITableView, willDisplayFooterView view: UIView, forSection section: Int) { + open func tableView(_ tableView: UITableView, willDisplayFooterView view: UIView, forSection section: Int) { let header: UITableViewHeaderFooterView = view as! UITableViewHeaderFooterView header.textLabel!.textColor = ActorSDK.sharedActor().style.cellFooterColor } - public func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { + open func tableView(_ tableView: UITableView, didSelectRowAtIndexPath indexPath: IndexPath) { - if indexPath.row == 0 { + if (indexPath as NSIndexPath).row == 0 { Actor.setPrivacyWithPrivacy("always") - } else if indexPath.row == 1 { + } else if (indexPath as NSIndexPath).row == 1 { Actor.setPrivacyWithPrivacy("contacts") - } else if indexPath.row == 2 { + } else if (indexPath as NSIndexPath).row == 2 { Actor.setPrivacyWithPrivacy("none") diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Settings/AASettingsMediaViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Settings/AASettingsMediaViewController.swift index 3498ceb88f..753b51e8d4 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Settings/AASettingsMediaViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Settings/AASettingsMediaViewController.swift @@ -4,12 +4,12 @@ import UIKit -public class AASettingsMediaViewController: AAContentTableController { +open class AASettingsMediaViewController: AAContentTableController { - private var sessionsCell: AAManagedArrayRows? + fileprivate var sessionsCell: AAManagedArrayRows? public init() { - super.init(style: AAContentTableStyle.SettingsGrouped) + super.init(style: AAContentTableStyle.settingsGrouped) navigationItem.title = AALocalized("MediaTitle") } @@ -18,14 +18,14 @@ public class AASettingsMediaViewController: AAContentTableController { fatalError("init(coder:) has not been implemented") } - public override func tableDidLoad() { + open override func tableDidLoad() { section { (s) -> () in s.headerText = AALocalized("MediaPhotoDownloadHeader") s.common { (r) -> () in - r.style = .Switch + r.style = .switch r.content = AALocalized("SettingsPrivateChats") r.switchOn = ActorSDK.sharedActor().isPhotoAutoDownloadPrivate @@ -36,7 +36,7 @@ public class AASettingsMediaViewController: AAContentTableController { } s.common { (r) -> () in - r.style = .Switch + r.style = .switch r.content = AALocalized("SettingsGroupChats") r.switchOn = ActorSDK.sharedActor().isPhotoAutoDownloadGroup @@ -52,7 +52,7 @@ public class AASettingsMediaViewController: AAContentTableController { s.headerText = AALocalized("MediaAudioDownloadHeader") s.common { (r) -> () in - r.style = .Switch + r.style = .switch r.content = AALocalized("SettingsPrivateChats") r.switchOn = ActorSDK.sharedActor().isAudioAutoDownloadPrivate @@ -63,7 +63,7 @@ public class AASettingsMediaViewController: AAContentTableController { } s.common { (r) -> () in - r.style = .Switch + r.style = .switch r.content = AALocalized("SettingsGroupChats") r.switchOn = ActorSDK.sharedActor().isAudioAutoDownloadGroup @@ -79,7 +79,7 @@ public class AASettingsMediaViewController: AAContentTableController { s.headerText = AALocalized("MediaOtherHeader") s.common { (r) -> () in - r.style = .Switch + r.style = .switch r.content = AALocalized("MediaAutoplayGif") r.switchOn = ActorSDK.sharedActor().isGIFAutoplayEnabled diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Settings/AASettingsNotificationsViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Settings/AASettingsNotificationsViewController.swift index 7a1f6b8ed5..2646e7dd99 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Settings/AASettingsNotificationsViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Settings/AASettingsNotificationsViewController.swift @@ -4,19 +4,19 @@ import UIKit -public class AASettingsNotificationsViewController: AATableViewController { +open class AASettingsNotificationsViewController: AATableViewController { // MARK: - // MARK: Constructors - private let CellIdentifier = "CellIdentifier" + fileprivate let CellIdentifier = "CellIdentifier" public init() { - super.init(style: UITableViewStyle.Grouped) + super.init(style: UITableViewStyle.grouped) title = AALocalized("NotificationsTitle") - content = ACAllEvents_Settings.NOTIFICATIONS() + content = ACAllEvents_Settings.notifications() } public required init(coder aDecoder: NSCoder) { @@ -25,12 +25,12 @@ public class AASettingsNotificationsViewController: AATableViewController { // MARK: - - public override func viewDidLoad() { + open override func viewDidLoad() { super.viewDidLoad() - tableView.registerClass(AACommonCell.self, forCellReuseIdentifier: CellIdentifier) + tableView.register(AACommonCell.self, forCellReuseIdentifier: CellIdentifier) tableView.backgroundColor = appStyle.vcBackyardColor - tableView.separatorStyle = UITableViewCellSeparatorStyle.None + tableView.separatorStyle = UITableViewCellSeparatorStyle.none view.backgroundColor = tableView.backgroundColor } @@ -38,11 +38,11 @@ public class AASettingsNotificationsViewController: AATableViewController { // MARK: - // MARK: UITableView Data Source - public override func numberOfSectionsInTableView(tableView: UITableView) -> Int { + open override func numberOfSections(in tableView: UITableView) -> Int { return 4 } - public override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + open override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { if (section == 0) { return 1 } else if (section == 1) { @@ -56,7 +56,7 @@ public class AASettingsNotificationsViewController: AATableViewController { return 1 } - public func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + open func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { if (section == 0) { return AALocalized("NotificationsEffectsTitle") } else if (section == 1) { @@ -70,7 +70,7 @@ public class AASettingsNotificationsViewController: AATableViewController { return nil } - public func tableView(tableView: UITableView, titleForFooterInSection section: Int) -> String? { + open func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { if (section == 1) { return AALocalized("NotificationsNotificationHint") } else if (section == 2) { @@ -82,12 +82,12 @@ public class AASettingsNotificationsViewController: AATableViewController { return nil } - private func notificationsTonesCell(indexPath: NSIndexPath) -> AACommonCell { - let cell = tableView.dequeueReusableCellWithIdentifier(CellIdentifier, forIndexPath: indexPath) as! AACommonCell + fileprivate func notificationsTonesCell(_ indexPath: IndexPath) -> AACommonCell { + let cell = tableView.dequeueReusableCell(withIdentifier: CellIdentifier, for: indexPath) as! AACommonCell cell.setContent(AALocalized("NotificationsSoundEffects")) - cell.style = .Switch - cell.selectionStyle = UITableViewCellSelectionStyle.None + cell.style = .switch + cell.selectionStyle = UITableViewCellSelectionStyle.none cell.bottomSeparatorVisible = true cell.topSeparatorVisible = true cell.bottomSeparatorLeftInset = 0 @@ -95,18 +95,18 @@ public class AASettingsNotificationsViewController: AATableViewController { cell.setSwitcherOn(Actor.isConversationTonesEnabled()) cell.switchBlock = { (nValue: Bool) in - Actor.changeConversationTonesEnabledWithValue(nValue) + Actor.changeConversationTonesEnabled(withValue: nValue) } return cell } - private func notificationsEnableCell(indexPath: NSIndexPath) -> AACommonCell { - let cell = tableView.dequeueReusableCellWithIdentifier(CellIdentifier, forIndexPath: indexPath) as! AACommonCell + fileprivate func notificationsEnableCell(_ indexPath: IndexPath) -> AACommonCell { + let cell = tableView.dequeueReusableCell(withIdentifier: CellIdentifier, for: indexPath) as! AACommonCell cell.setContent(AALocalized("NotificationsEnable")) - cell.style = .Switch - cell.selectionStyle = UITableViewCellSelectionStyle.None + cell.style = .switch + cell.selectionStyle = UITableViewCellSelectionStyle.none cell.bottomSeparatorVisible = true cell.topSeparatorVisible = true cell.bottomSeparatorLeftInset = 0 @@ -115,12 +115,12 @@ public class AASettingsNotificationsViewController: AATableViewController { cell.setSwitcherOn(Actor.isNotificationsEnabled()) cell.switchBlock = { (nValue: Bool) in self.tableView.beginUpdates() - Actor.changeNotificationsEnabledWithValue(nValue) - let rows = [NSIndexPath(forRow: 1, inSection: indexPath.section)] + Actor.changeNotificationsEnabled(withValue: nValue) + let rows = [IndexPath(row: 1, section: (indexPath as NSIndexPath).section)] if (nValue) { - self.tableView.insertRowsAtIndexPaths(rows, withRowAnimation: UITableViewRowAnimation.Middle) + self.tableView.insertRows(at: rows, with: UITableViewRowAnimation.middle) } else { - self.tableView.deleteRowsAtIndexPaths(rows, withRowAnimation: UITableViewRowAnimation.Middle) + self.tableView.deleteRows(at: rows, with: UITableViewRowAnimation.middle) } self.tableView.endUpdates() } @@ -128,12 +128,12 @@ public class AASettingsNotificationsViewController: AATableViewController { return cell } - private func notificationsAlertCell(indexPath: NSIndexPath) -> AACommonCell { - let cell = tableView.dequeueReusableCellWithIdentifier(CellIdentifier, forIndexPath: indexPath) as! AACommonCell + fileprivate func notificationsAlertCell(_ indexPath: IndexPath) -> AACommonCell { + let cell = tableView.dequeueReusableCell(withIdentifier: CellIdentifier, for: indexPath) as! AACommonCell cell.setContent(AALocalized("NotificationsSound")) - cell.style = .Switch - cell.selectionStyle = UITableViewCellSelectionStyle.None + cell.style = .switch + cell.selectionStyle = UITableViewCellSelectionStyle.none cell.bottomSeparatorVisible = true //cell.topSeparatorVisible = true cell.bottomSeparatorLeftInset = 0 @@ -142,19 +142,19 @@ public class AASettingsNotificationsViewController: AATableViewController { cell.setSwitcherOn(Actor.isNotificationSoundEnabled()) cell.setSwitcherEnabled(Actor.isNotificationsEnabled()) cell.switchBlock = { (nValue: Bool) in - Actor.changeNotificationSoundEnabledWithValue(nValue) + Actor.changeNotificationSoundEnabled(withValue: nValue) } return cell } - private func groupEnabledCell(indexPath: NSIndexPath) -> AACommonCell { - let cell = tableView.dequeueReusableCellWithIdentifier(CellIdentifier, forIndexPath: indexPath) as! AACommonCell + fileprivate func groupEnabledCell(_ indexPath: IndexPath) -> AACommonCell { + let cell = tableView.dequeueReusableCell(withIdentifier: CellIdentifier, for: indexPath) as! AACommonCell cell.setContent(AALocalized("NotificationsEnable")) - cell.style = .Switch - cell.selectionStyle = UITableViewCellSelectionStyle.None + cell.style = .switch + cell.selectionStyle = UITableViewCellSelectionStyle.none cell.topSeparatorVisible = true cell.bottomSeparatorVisible = true cell.bottomSeparatorLeftInset = 0 @@ -164,11 +164,11 @@ public class AASettingsNotificationsViewController: AATableViewController { cell.switchBlock = { (nValue: Bool) in self.tableView.beginUpdates() Actor.changeGroupNotificationsEnabled(nValue) - let rows = [NSIndexPath(forRow: 1, inSection: indexPath.section)] + let rows = [IndexPath(row: 1, section: (indexPath as NSIndexPath).section)] if (nValue) { - self.tableView.insertRowsAtIndexPaths(rows, withRowAnimation: UITableViewRowAnimation.Middle) + self.tableView.insertRows(at: rows, with: UITableViewRowAnimation.middle) } else { - self.tableView.deleteRowsAtIndexPaths(rows, withRowAnimation: UITableViewRowAnimation.Middle) + self.tableView.deleteRows(at: rows, with: UITableViewRowAnimation.middle) } self.tableView.endUpdates() } @@ -176,12 +176,12 @@ public class AASettingsNotificationsViewController: AATableViewController { return cell } - private func groupEnabledMentionsCell(indexPath: NSIndexPath) -> AACommonCell { - let cell = tableView.dequeueReusableCellWithIdentifier(CellIdentifier, forIndexPath: indexPath) as! AACommonCell + fileprivate func groupEnabledMentionsCell(_ indexPath: IndexPath) -> AACommonCell { + let cell = tableView.dequeueReusableCell(withIdentifier: CellIdentifier, for: indexPath) as! AACommonCell cell.setContent(AALocalized("NotificationsOnlyMentions")) - cell.style = .Switch - cell.selectionStyle = UITableViewCellSelectionStyle.None + cell.style = .switch + cell.selectionStyle = UITableViewCellSelectionStyle.none cell.bottomSeparatorVisible = true //cell.topSeparatorVisible = true cell.bottomSeparatorLeftInset = 0 @@ -196,12 +196,12 @@ public class AASettingsNotificationsViewController: AATableViewController { return cell } - private func inAppAlertCell(indexPath: NSIndexPath) -> AACommonCell { - let cell = tableView.dequeueReusableCellWithIdentifier(CellIdentifier, forIndexPath: indexPath) as! AACommonCell + fileprivate func inAppAlertCell(_ indexPath: IndexPath) -> AACommonCell { + let cell = tableView.dequeueReusableCell(withIdentifier: CellIdentifier, for: indexPath) as! AACommonCell cell.setContent(AALocalized("NotificationsEnable")) - cell.style = .Switch - cell.selectionStyle = UITableViewCellSelectionStyle.None + cell.style = .switch + cell.selectionStyle = UITableViewCellSelectionStyle.none cell.bottomSeparatorVisible = true cell.topSeparatorVisible = true cell.bottomSeparatorLeftInset = 0 @@ -210,12 +210,12 @@ public class AASettingsNotificationsViewController: AATableViewController { cell.setSwitcherOn(Actor.isInAppNotificationsEnabled()) cell.switchBlock = { (nValue: Bool) in self.tableView.beginUpdates() - Actor.changeInAppNotificationsEnabledWithValue(nValue) - let rows = [NSIndexPath(forRow: 1, inSection: 3), NSIndexPath(forRow: 2, inSection: 3)] + Actor.changeInAppNotificationsEnabled(withValue: nValue) + let rows = [IndexPath(row: 1, section: 3), IndexPath(row: 2, section: 3)] if (nValue) { - self.tableView.insertRowsAtIndexPaths(rows, withRowAnimation: UITableViewRowAnimation.Middle) + self.tableView.insertRows(at: rows, with: UITableViewRowAnimation.middle) } else { - self.tableView.deleteRowsAtIndexPaths(rows, withRowAnimation: UITableViewRowAnimation.Middle) + self.tableView.deleteRows(at: rows, with: UITableViewRowAnimation.middle) } self.tableView.endUpdates() } @@ -223,12 +223,12 @@ public class AASettingsNotificationsViewController: AATableViewController { return cell } - private func inAppSoundCell(indexPath: NSIndexPath) -> AACommonCell { - let cell = tableView.dequeueReusableCellWithIdentifier(CellIdentifier, forIndexPath: indexPath) as! AACommonCell + fileprivate func inAppSoundCell(_ indexPath: IndexPath) -> AACommonCell { + let cell = tableView.dequeueReusableCell(withIdentifier: CellIdentifier, for: indexPath) as! AACommonCell cell.setContent(AALocalized("NotificationsSound")) - cell.style = .Switch - cell.selectionStyle = UITableViewCellSelectionStyle.None + cell.style = .switch + cell.selectionStyle = UITableViewCellSelectionStyle.none cell.bottomSeparatorVisible = true // cell.topSeparatorVisible = true cell.bottomSeparatorLeftInset = 0 @@ -238,19 +238,19 @@ public class AASettingsNotificationsViewController: AATableViewController { cell.setSwitcherEnabled(Actor.isInAppNotificationsEnabled()) cell.switchBlock = { (nValue: Bool) in - Actor.changeInAppNotificationSoundEnabledWithValue(nValue) + Actor.changeInAppNotificationSoundEnabled(withValue: nValue) } return cell } - private func inAppVibrateCell(indexPath: NSIndexPath) -> AACommonCell { - let cell = tableView.dequeueReusableCellWithIdentifier(CellIdentifier, forIndexPath: indexPath) as! AACommonCell + fileprivate func inAppVibrateCell(_ indexPath: IndexPath) -> AACommonCell { + let cell = tableView.dequeueReusableCell(withIdentifier: CellIdentifier, for: indexPath) as! AACommonCell cell.setContent(AALocalized("NotificationsVibration")) - cell.style = .Switch - cell.selectionStyle = UITableViewCellSelectionStyle.None + cell.style = .switch + cell.selectionStyle = UITableViewCellSelectionStyle.none cell.bottomSeparatorVisible = true //cell.topSeparatorVisible = true cell.bottomSeparatorLeftInset = 0 @@ -260,18 +260,18 @@ public class AASettingsNotificationsViewController: AATableViewController { cell.setSwitcherEnabled(Actor.isInAppNotificationsEnabled()) cell.switchBlock = { (nValue: Bool) in - Actor.changeInAppNotificationVibrationEnabledWithValue(nValue) + Actor.changeInAppNotificationVibrationEnabled(withValue: nValue) } return cell } - private func notificationsPreviewCell(indexPath: NSIndexPath) -> AACommonCell { - let cell = tableView.dequeueReusableCellWithIdentifier(CellIdentifier, forIndexPath: indexPath) as! AACommonCell + fileprivate func notificationsPreviewCell(_ indexPath: IndexPath) -> AACommonCell { + let cell = tableView.dequeueReusableCell(withIdentifier: CellIdentifier, for: indexPath) as! AACommonCell cell.setContent(AALocalized("NotificationsPreview")) - cell.style = .Switch - cell.selectionStyle = UITableViewCellSelectionStyle.None + cell.style = .switch + cell.selectionStyle = UITableViewCellSelectionStyle.none cell.bottomSeparatorVisible = true cell.topSeparatorVisible = true cell.bottomSeparatorLeftInset = 0 @@ -279,40 +279,40 @@ public class AASettingsNotificationsViewController: AATableViewController { cell.setSwitcherOn(Actor.isShowNotificationsText()) cell.switchBlock = { (nValue: Bool) in - Actor.changeShowNotificationTextEnabledWithValue(nValue) + Actor.changeShowNotificationTextEnabled(withValue: nValue) } return cell } - public override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { - if indexPath.section == 0 { + open override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + if (indexPath as NSIndexPath).section == 0 { return notificationsTonesCell(indexPath) - } else if (indexPath.section == 1) { - if (indexPath.row == 0) { + } else if ((indexPath as NSIndexPath).section == 1) { + if ((indexPath as NSIndexPath).row == 0) { return notificationsEnableCell(indexPath) - } else if (indexPath.row == 1) { + } else if ((indexPath as NSIndexPath).row == 1) { return notificationsAlertCell(indexPath) } - } else if (indexPath.section == 2) { - if (indexPath.row == 0) { + } else if ((indexPath as NSIndexPath).section == 2) { + if ((indexPath as NSIndexPath).row == 0) { return groupEnabledCell(indexPath) } else { return groupEnabledMentionsCell(indexPath) } - } else if (indexPath.section == 3) { + } else if ((indexPath as NSIndexPath).section == 3) { return notificationsPreviewCell(indexPath) } return UITableViewCell() } - public func tableView(tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) { + open func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) { let header: UITableViewHeaderFooterView = view as! UITableViewHeaderFooterView header.textLabel!.textColor = ActorSDK.sharedActor().style.cellFooterColor } - public func tableView(tableView: UITableView, willDisplayFooterView view: UIView, forSection section: Int) { + open func tableView(_ tableView: UITableView, willDisplayFooterView view: UIView, forSection section: Int) { let header: UITableViewHeaderFooterView = view as! UITableViewHeaderFooterView header.textLabel!.textColor = ActorSDK.sharedActor().style.cellFooterColor } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Settings/AASettingsPrivacyViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Settings/AASettingsPrivacyViewController.swift index 016acc956f..bf92ea9aa2 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Settings/AASettingsPrivacyViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Settings/AASettingsPrivacyViewController.swift @@ -4,23 +4,23 @@ import UIKit -public class AASettingsPrivacyViewController: AAContentTableController { +open class AASettingsPrivacyViewController: AAContentTableController { - private var sessionsCell: AAManagedArrayRows? + fileprivate var sessionsCell: AAManagedArrayRows? public init() { - super.init(style: AAContentTableStyle.SettingsGrouped) + super.init(style: AAContentTableStyle.settingsGrouped) navigationItem.title = AALocalized("PrivacyTitle") - content = ACAllEvents_Settings.PRIVACY() + content = ACAllEvents_Settings.privacy() } public required init(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - public override func tableDidLoad() { + open override func tableDidLoad() { section { (s) -> () in diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Settings/AASettingsSessionsController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Settings/AASettingsSessionsController.swift index 965c1019c1..eff004cdf0 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Settings/AASettingsSessionsController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Settings/AASettingsSessionsController.swift @@ -4,23 +4,23 @@ import UIKit -public class AASettingsSessionsController: AAContentTableController { +open class AASettingsSessionsController: AAContentTableController { - private var sessionsCell: AAManagedArrayRows? + fileprivate var sessionsCell: AAManagedArrayRows? public init() { - super.init(style: AAContentTableStyle.SettingsGrouped) + super.init(style: AAContentTableStyle.settingsGrouped) navigationItem.title = AALocalized("PrivacyAllSessions") - content = ACAllEvents_Settings.PRIVACY() + content = ACAllEvents_Settings.privacy() } public required init(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - public override func tableDidLoad() { + open override func tableDidLoad() { section { (s) -> () in @@ -42,20 +42,20 @@ public class AASettingsSessionsController: AAContentTableController { section { (s) -> () in self.sessionsCell = s.arrays() { (r: AAManagedArrayRows) -> () in r.bindData = { (c: AACommonCell, d: ARApiAuthSession) -> () in - if d.getAuthHolder().ordinal() != ARApiAuthHolder.THISDEVICE().ordinal() { - c.style = .Normal + if d.getAuthHolder().ordinal() != ARApiAuthHolder.thisdevice().ordinal() { + c.style = .normal c.setContent(d.getDeviceTitle()) } else { - c.style = .Hint + c.style = .hint c.setContent("(Current) \(d.getDeviceTitle())") } } r.selectAction = { (d) -> Bool in - if d.getAuthHolder().ordinal() != ARApiAuthHolder.THISDEVICE().ordinal() { + if d.getAuthHolder().ordinal() != ARApiAuthHolder.thisdevice().ordinal() { self.confirmDangerSheetUser("PrivacyTerminateAlertSingle", tapYes: { [unowned self] () -> () in // Terminating session and reload list - self.executeSafe(Actor.terminateSessionCommandWithId(d.getId()), successBlock: { [unowned self] (val) -> Void in + self.executeSafe(Actor.terminateSessionCommand(withId: d.getId()), successBlock: { [unowned self] (val) -> Void in self.loadSessions() }) }, tapNo: nil) @@ -70,7 +70,7 @@ public class AASettingsSessionsController: AAContentTableController { loadSessions() } - private func loadSessions() { + fileprivate func loadSessions() { execute(Actor.loadSessionsCommand(), successBlock: { [unowned self] (val) -> Void in self.sessionsCell!.data = (val as! JavaUtilList).toArray().toSwiftArray() self.managedTable.tableView.reloadData() diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Settings/AASettingsViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Settings/AASettingsViewController.swift index 1d7b8bb237..fcdfb1409f 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Settings/AASettingsViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Settings/AASettingsViewController.swift @@ -5,21 +5,21 @@ import UIKit import MobileCoreServices -public class AASettingsViewController: AAContentTableController { +open class AASettingsViewController: AAContentTableController { - private var phonesCells: AAManagedArrayRows! - private var emailCells: AAManagedArrayRows! + fileprivate var phonesCells: AAManagedArrayRows! + fileprivate var emailCells: AAManagedArrayRows! - private var headerCell: AAAvatarRow! - private var nicknameCell: AATitledRow! - private var aboutCell: AATitledRow! + fileprivate var headerCell: AAAvatarRow! + fileprivate var nicknameCell: AATitledRow! + fileprivate var aboutCell: AATitledRow! public init() { - super.init(style: AAContentTableStyle.SettingsPlain) + super.init(style: AAContentTableStyle.settingsPlain) uid = Int(Actor.myUid()) - content = ACAllEvents_Main.SETTINGS() + content = ACAllEvents_Main.settings() tabBarItem = UITabBarItem(title: "TabSettings", img: "TabIconSettings", selImage: "TabIconSettingsHighlighted") @@ -30,7 +30,7 @@ public class AASettingsViewController: AAContentTableController { fatalError("init(coder:) has not been implemented") } - public override func tableDidLoad() { + open override func tableDidLoad() { // Profile section { [unowned self] (s) -> () in @@ -43,13 +43,13 @@ public class AASettingsViewController: AAContentTableController { let upload = Actor.getOwnAvatarVM()!.uploadState.get() as? ACAvatarUploadState let avatar = self.user.getAvatarModel().get() let presence = self.user.getPresenceModel().get() - let presenceText = Actor.getFormatter().formatPresence(presence, withSex: self.user.getSex()) + let presenceText = Actor.getFormatter().formatPresence(presence, with: self.user.getSex()) let name = self.user.getNameModel().get() r.id = self.uid r.title = name - if (upload != nil && upload!.isUploading.boolValue) { + if (upload != nil && upload!.isUploading) { r.avatar = nil r.avatarPath = upload!.descriptor r.avatarLoading = true @@ -61,7 +61,7 @@ public class AASettingsViewController: AAContentTableController { if presenceText != nil { r.subtitle = presenceText - if presence!.state.ordinal() == ACUserPresence_State.ONLINE().ordinal() { + if presence!.state.ordinal() == ACUserPresence_State.online().ordinal() { r.subtitleColor = ActorSDK.sharedActor().style.userOnlineColor } else { r.subtitleColor = ActorSDK.sharedActor().style.userOfflineColor @@ -73,13 +73,13 @@ public class AASettingsViewController: AAContentTableController { r.avatarDidTap = { [unowned self] (view: UIView) -> () in let avatar = self.user.getAvatarModel().get() - if avatar != nil && avatar.fullImage != nil { + if avatar != nil && avatar?.fullImage != nil { - let full = avatar.fullImage.fileReference - let small = avatar.smallImage.fileReference - let size = CGSize(width: Int(avatar.fullImage.width), height: Int(avatar.fullImage.height)) + let full = avatar?.fullImage.fileReference + let small = avatar?.smallImage.fileReference + let size = CGSize(width: Int((avatar?.fullImage.width)!), height: Int((avatar?.fullImage.height)!)) - self.presentViewController(AAPhotoPreviewController(file: full, previewFile: small, size: size, fromView: view), animated: true, completion: nil) + self.present(AAPhotoPreviewController(file: full!, previewFile: small, size: size, fromView: view), animated: true, completion: nil) } } } @@ -87,8 +87,8 @@ public class AASettingsViewController: AAContentTableController { // Profile: Set Photo s.action("SettingsSetPhoto") { [unowned self] (r) -> () in r.selectAction = { [unowned self] () -> Bool in - let hasCamera = UIImagePickerController.isSourceTypeAvailable(UIImagePickerControllerSourceType.Camera) - let view = self.tableView.cellForRowAtIndexPath(NSIndexPath(forRow: 1, inSection: 0))!.contentView + let hasCamera = UIImagePickerController.isSourceTypeAvailable(UIImagePickerControllerSourceType.camera) + let view = self.tableView.cellForRow(at: IndexPath(row: 1, section: 0))!.contentView self.showActionSheet(hasCamera ? ["PhotoCamera", "PhotoLibrary"] : ["PhotoLibrary"], cancelButton: "AlertCancel", destructButton: self.user.getAvatarModel().get() != nil ? "PhotoRemove" : nil, @@ -122,7 +122,7 @@ public class AASettingsViewController: AAContentTableController { c.initialText = self.user.getNameModel().get() - c.fieldAutocapitalizationType = .Words + c.fieldAutocapitalizationType = .words c.fieldHint = "SettingsEditFieldHint" c.didDoneTap = { (t, c) -> () in @@ -131,7 +131,7 @@ public class AASettingsViewController: AAContentTableController { return } - c.executeSafeOnlySuccess(Actor.editMyNameCommandWithName(t)!) { (val) -> Void in + c.executeSafeOnlySuccess(Actor.editMyNameCommand(withName: t)!) { (val) -> Void in c.dismiss() } } @@ -165,7 +165,7 @@ public class AASettingsViewController: AAContentTableController { r.height = 230 r.closure = { [unowned self] (cell) -> () in cell.wallpapperDidTap = { [unowned self] (name) -> () in - self.presentViewController(AAWallpapperPreviewController(imageName: name), animated: true, completion: nil) + self.present(AAWallpapperPreviewController(imageName: name), animated: true, completion: nil) } cell.bind() } @@ -186,7 +186,7 @@ public class AASettingsViewController: AAContentTableController { // Contacts: Nicknames self.nicknameCell = s.titled("ProfileUsername") { [unowned self] (r) -> () in - r.accessoryType = .DisclosureIndicator + r.accessoryType = .disclosureIndicator r.bindAction = { [unowned self] (r) -> () in if let nick = self.user.getNickModel().get() { @@ -210,8 +210,8 @@ public class AASettingsViewController: AAContentTableController { } c.fieldHint = "SettingsUsernameHintField" - c.fieldAutocorrectionType = .No - c.fieldAutocapitalizationType = .None + c.fieldAutocorrectionType = .no + c.fieldAutocapitalizationType = .none c.hint = "SettingsUsernameHint" c.didDoneTap = { (t, c) -> () in @@ -219,7 +219,7 @@ public class AASettingsViewController: AAContentTableController { if nNick?.length == 0 { nNick = nil } - c.executeSafeOnlySuccess(Actor.editMyNickCommandWithNick(nNick)!, successBlock: { (val) -> Void in + c.executeSafeOnlySuccess(Actor.editMyNickCommand(withNick: nNick)!, successBlock: { (val) -> Void in c.dismiss() }) } @@ -232,7 +232,7 @@ public class AASettingsViewController: AAContentTableController { // Contacts: About self.aboutCell = s.titled("ProfileAbout") { [unowned self] (r) -> () in - r.accessoryType = .DisclosureIndicator + r.accessoryType = .disclosureIndicator r.bindAction = { [unowned self] (r) -> () in if let about = self.user.getAboutModel().get() { @@ -260,7 +260,7 @@ public class AASettingsViewController: AAContentTableController { if updatedText?.length == 0 { updatedText = nil } - controller.executeSafeOnlySuccess(Actor.editMyAboutCommandWithNick(updatedText)!, successBlock: { (val) -> Void in + controller.executeSafeOnlySuccess(Actor.editMyAboutCommand(withNick: updatedText)!, successBlock: { (val) -> Void in controller.dismiss() }) } @@ -280,7 +280,7 @@ public class AASettingsViewController: AAContentTableController { r.bindData = { (c: AATitledCell, d: ACUserPhone) -> () in c.setContent(AALocalized("SettingsMobilePhone"), content: "+\(d.phone)", isAction: false) - c.accessoryType = .None + c.accessoryType = .none } r.bindCopy = { (d: ACUserPhone) -> String? in @@ -288,9 +288,9 @@ public class AASettingsViewController: AAContentTableController { } r.selectAction = { [unowned self] (d: ACUserPhone) -> Bool in - let hasPhone = UIApplication.sharedApplication().canOpenURL(NSURL(string: "telprompt://")!) + let hasPhone = UIApplication.shared.canOpenURL(URL(string: "telprompt://")!) if (!hasPhone) { - UIPasteboard.generalPasteboard().string = "+\(d.phone)" + UIPasteboard.general.string = "+\(d.phone)" self.alertUser("NumberCopied") } return true @@ -305,7 +305,7 @@ public class AASettingsViewController: AAContentTableController { r.bindData = { (c: AATitledCell, d: ACUserEmail) -> () in c.setContent(d.title, content: d.email, isAction: false) - c.accessoryType = .None + c.accessoryType = .none } r.bindCopy = { (d: ACUserEmail) -> String? in @@ -329,19 +329,19 @@ public class AASettingsViewController: AAContentTableController { if let account = ActorSDK.sharedActor().supportAccount { s.navigate("SettingsAskQuestion", closure: { (r) -> () in r.selectAction = { () -> Bool in - self.executeSafe(Actor.findUsersCommandWithQuery(account)) { (val) -> Void in + self.executeSafe(Actor.findUsersCommand(withQuery: account)) { (val) -> Void in var user:ACUserVM! if let users = val as? IOSObjectArray { if Int(users.length()) > 0 { - if let tempUser = users.objectAtIndex(0) as? ACUserVM { + if let tempUser = users.object(at: 0) as? ACUserVM { user = tempUser } } } - if let customController = ActorSDK.sharedActor().delegate.actorControllerForConversation(ACPeer.userWithInt(user.getId())) { + if let customController = ActorSDK.sharedActor().delegate.actorControllerForConversation(ACPeer.user(with: user.getId())) { self.navigateDetail(customController) } else { - self.navigateDetail(ConversationViewController(peer: ACPeer.userWithInt(user.getId()))) + self.navigateDetail(ConversationViewController(peer: ACPeer.user(with: user.getId()))) } } return true @@ -360,14 +360,14 @@ public class AASettingsViewController: AAContentTableController { } // Support: App version - let version = NSBundle.mainBundle().infoDictionary!["CFBundleShortVersionString"] as! String + let version = Bundle.main.infoDictionary!["CFBundleShortVersionString"] as! String s.hint(AALocalized("SettingsVersion").replace("{version}", dest: version)) ActorSDK.sharedActor().delegate.actorSettingsSupportDidCreated(self, section: s) } } - public override func tableWillBind(binder: AABinder) { + open override func tableWillBind(_ binder: AABinder) { // Header diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Settings/AASettingsWallpapersController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Settings/AASettingsWallpapersController.swift index 9ec3dea054..4830589d12 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Settings/AASettingsWallpapersController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Settings/AASettingsWallpapersController.swift @@ -11,11 +11,11 @@ class AASettingsWallpapersController: AATableViewController { // MARK: - // MARK: Constructors - private let CellIdentifier = "CellIdentifier" + fileprivate let CellIdentifier = "CellIdentifier" init() { - super.init(style: UITableViewStyle.Grouped) + super.init(style: UITableViewStyle.grouped) title = AALocalized("WallpapersTitle") } @@ -27,7 +27,7 @@ class AASettingsWallpapersController: AATableViewController { override func viewDidLoad() { super.viewDidLoad() - tableView.registerClass(AAWallpapersCell.self, forCellReuseIdentifier: CellIdentifier) + tableView.register(AAWallpapersCell.self, forCellReuseIdentifier: CellIdentifier) tableView.backgroundColor = appStyle.vcBackyardColor tableView.separatorColor = appStyle.vcSeparatorColor @@ -37,45 +37,45 @@ class AASettingsWallpapersController: AATableViewController { // MARK: - // MARK: UITableView Data Source - override func numberOfSectionsInTableView(tableView: UITableView) -> Int { + override func numberOfSections(in tableView: UITableView) -> Int { return 2 } - override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 1 } - override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { - if indexPath.section == 0 { + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + if (indexPath as NSIndexPath).section == 0 { return photosLibrary(indexPath) } else { return wallpapersCell(indexPath) } } - func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { + func tableView(_ tableView: UITableView, didSelectRowAtIndexPath indexPath: IndexPath) { // - self.tableView.deselectRowAtIndexPath(indexPath, animated: true) + self.tableView.deselectRow(at: indexPath, animated: true) - if indexPath.section == 0 { + if (indexPath as NSIndexPath).section == 0 { - self.pickImage(.PhotoLibrary) + self.pickImage(.photoLibrary) } } - func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { return nil } - func tableView(tableView: UITableView, titleForFooterInSection section: Int) -> String? { + func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { return nil } - func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { - if indexPath.section == 0 { + func tableView(_ tableView: UITableView, heightForRowAtIndexPath indexPath: IndexPath) -> CGFloat { + if (indexPath as NSIndexPath).section == 0 { return 40 } else { return 180 @@ -85,23 +85,23 @@ class AASettingsWallpapersController: AATableViewController { // MARK: - // MARK: Create cells - private func photosLibrary(indexPath: NSIndexPath) -> AACommonCell { + fileprivate func photosLibrary(_ indexPath: IndexPath) -> AACommonCell { let cell = AACommonCell() cell.textLabel?.text = AALocalized("WallpapersPhoto") - cell.style = .Navigation + cell.style = .navigation cell.textLabel?.textColor = appStyle.cellTextColor return cell } - private func wallpapersCell(indexPath: NSIndexPath) -> AAWallpapersCell { - let cell = tableView.dequeueReusableCellWithIdentifier(CellIdentifier, forIndexPath: indexPath) as! AAWallpapersCell + fileprivate func wallpapersCell(_ indexPath: IndexPath) -> AAWallpapersCell { + let cell = tableView.dequeueReusableCell(withIdentifier: CellIdentifier, for: indexPath) as! AAWallpapersCell - cell.selectionStyle = UITableViewCellSelectionStyle.None + cell.selectionStyle = UITableViewCellSelectionStyle.none cell.wallpapperDidTap = { [unowned self] (name) -> () in - self.presentViewController(AAWallpapperPreviewController(imageName: name), animated: true, completion: nil) + self.present(AAWallpapperPreviewController(imageName: name), animated: true, completion: nil) } return cell @@ -110,13 +110,13 @@ class AASettingsWallpapersController: AATableViewController { // MARK: - // MARK: Picker delegate - override func imagePickerController(picker: UIImagePickerController, didFinishPickingImage image: UIImage, editingInfo: [String : AnyObject]?) { + override func imagePickerController(_ picker: UIImagePickerController, didFinishPickingImage image: UIImage, editingInfo: [String : AnyObject]?) { picker.navigationController?.pushViewController(AAWallpapperPreviewController(selectedImage: image), animated: true) } - override func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) { + override func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) { if let image = info[UIImagePickerControllerOriginalImage] as? UIImage { picker.pushViewController(AAWallpapperPreviewController(selectedImage: image), animated: true) @@ -124,20 +124,20 @@ class AASettingsWallpapersController: AATableViewController { } - override func imagePickerControllerDidCancel(picker: UIImagePickerController) { - picker.dismissViewControllerAnimated(true, completion: nil) + override func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { + picker.dismiss(animated: true, completion: nil) } // MARK: - // MARK: Image picking - func pickImage(source: UIImagePickerControllerSourceType) { + func pickImage(_ source: UIImagePickerControllerSourceType) { let pickerController = AAImagePickerController() pickerController.sourceType = source pickerController.mediaTypes = [kUTTypeImage as String] pickerController.delegate = self - self.presentViewController(pickerController, animated: true, completion: nil) + self.present(pickerController, animated: true, completion: nil) } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Settings/AASettingsWallpapper.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Settings/AASettingsWallpapper.swift index ad9613b6dc..88094655ce 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Settings/AASettingsWallpapper.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Settings/AASettingsWallpapper.swift @@ -4,7 +4,7 @@ import Foundation -public class AASettingsWallpapper: AACollectionViewController, UICollectionViewDelegateFlowLayout { +open class AASettingsWallpapper: AACollectionViewController, UICollectionViewDelegateFlowLayout { let padding: CGFloat = 8 @@ -13,7 +13,7 @@ public class AASettingsWallpapper: AACollectionViewController, UICollectionViewD navigationItem.title = AALocalized("WallpapersTitle") - collectionView.registerClass(AAWallpapperPreviewCell.self, forCellWithReuseIdentifier: "cell") + collectionView.register(AAWallpapperPreviewCell.self, forCellWithReuseIdentifier: "cell") collectionView.contentInset = UIEdgeInsets(top: padding, left: padding, bottom: padding, right: padding) collectionView.backgroundColor = ActorSDK.sharedActor().style.vcBgColor view.backgroundColor = ActorSDK.sharedActor().style.vcBgColor @@ -23,29 +23,29 @@ public class AASettingsWallpapper: AACollectionViewController, UICollectionViewD fatalError("init(coder:) has not been implemented") } - override public func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + override open func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return 100 } - override public func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell { - let res = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as! AAWallpapperPreviewCell - res.bind(indexPath.item % 3) + override open func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let res = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! AAWallpapperPreviewCell + res.bind((indexPath as NSIndexPath).item % 3) return res } - public func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize { + open func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { let w = (collectionView.width - 4 * padding) / 3 - let h = w * (UIScreen.mainScreen().bounds.height / UIScreen.mainScreen().bounds.width) + let h = w * (UIScreen.main.bounds.height / UIScreen.main.bounds.width) return CGSize(width: w, height: h) } - public func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAtIndex section: Int) -> CGFloat { + open func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat { return padding } - public func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAtIndex section: Int) -> CGFloat { + open func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat { return padding } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Settings/AAWallpapersCell.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Settings/AAWallpapersCell.swift index d2dec5bfe4..a7f1fae686 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Settings/AAWallpapersCell.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Settings/AAWallpapersCell.swift @@ -4,44 +4,44 @@ import UIKit -public class AAWallpapersCell: AATableViewCell { +open class AAWallpapersCell: AATableViewCell { - private let wallpapper1 = UIImageView() - private let wallpapper2 = UIImageView() - private let wallpapper3 = UIImageView() + fileprivate let wallpapper1 = UIImageView() + fileprivate let wallpapper2 = UIImageView() + fileprivate let wallpapper3 = UIImageView() - public var wallpapperDidTap: ((name: String) -> ())? + open var wallpapperDidTap: ((_ name: String) -> ())? public override init(style: UITableViewCellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) wallpapper1.clipsToBounds = true - wallpapper1.contentMode = .ScaleAspectFill + wallpapper1.contentMode = .scaleAspectFill wallpapper1.image = UIImage.bundled("bg_1_preview.jpg")! - wallpapper1.viewDidTap = { [unowned self] () -> () in self.wallpapperDidTap?(name: "bg_1.jpg") } + wallpapper1.viewDidTap = { [unowned self] () -> () in self.wallpapperDidTap?("bg_1.jpg") } wallpapper2.clipsToBounds = true - wallpapper2.contentMode = .ScaleAspectFill + wallpapper2.contentMode = .scaleAspectFill wallpapper2.image = UIImage.bundled("bg_2_preview.jpg")! - wallpapper2.viewDidTap = { [unowned self] () -> () in self.wallpapperDidTap?(name: "bg_2.jpg") } + wallpapper2.viewDidTap = { [unowned self] () -> () in self.wallpapperDidTap?("bg_2.jpg") } wallpapper3.clipsToBounds = true - wallpapper3.contentMode = .ScaleAspectFill + wallpapper3.contentMode = .scaleAspectFill wallpapper3.image = UIImage.bundled("bg_3_preview.jpg")! - wallpapper3.viewDidTap = { [unowned self] () -> () in self.wallpapperDidTap?(name: "bg_3.jpg") } + wallpapper3.viewDidTap = { [unowned self] () -> () in self.wallpapperDidTap?("bg_3.jpg") } self.contentView.addSubview(wallpapper1) self.contentView.addSubview(wallpapper2) self.contentView.addSubview(wallpapper3) - selectionStyle = .None + selectionStyle = .none } public required init(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - public override func layoutSubviews() { + open override func layoutSubviews() { super.layoutSubviews() let width = contentView.width @@ -51,9 +51,9 @@ public class AAWallpapersCell: AATableViewCell { let wWidth = (width - padding * 4) / 3 let wHeight = height - padding - 15 - wallpapper1.frame = CGRectMake(padding, wPadding, wWidth, wHeight) - wallpapper2.frame = CGRectMake(padding * 2 + wWidth, wPadding, wWidth, wHeight) - wallpapper3.frame = CGRectMake(padding * 3 + wWidth * 2, wPadding, wWidth, wHeight) + wallpapper1.frame = CGRect(x: padding, y: wPadding, width: wWidth, height: wHeight) + wallpapper2.frame = CGRect(x: padding * 2 + wWidth, y: wPadding, width: wWidth, height: wHeight) + wallpapper3.frame = CGRect(x: padding * 3 + wWidth * 2, y: wPadding, width: wWidth, height: wHeight) } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Settings/Cells/AAWallpapperPreviewCell.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Settings/Cells/AAWallpapperPreviewCell.swift index d8212e0cd5..bd4c3003f3 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Settings/Cells/AAWallpapperPreviewCell.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Settings/Cells/AAWallpapperPreviewCell.swift @@ -6,13 +6,13 @@ import Foundation class AAWallpapperPreviewCell: UICollectionViewCell { - private let imageView = UIImageView() - private let imageIcon = UIImageView() + fileprivate let imageView = UIImageView() + fileprivate let imageIcon = UIImageView() override init(frame: CGRect) { super.init(frame: frame) - imageView.contentMode = .ScaleAspectFill + imageView.contentMode = .scaleAspectFill imageView.clipsToBounds = true imageIcon.image = UIImage.bundled("ImageSelectedOn") @@ -34,15 +34,15 @@ class AAWallpapperPreviewCell: UICollectionViewCell { fatalError("init(coder:) has not been implemented") } - func bind(index: Int) { + func bind(_ index: Int) { imageView.image = UIImage.bundled("bg_\(index + 1).jpg") - imageIcon.hidden = ActorSDK.sharedActor().messenger.getSelectedWallpaper() == "local:bg_\(index + 1).jpg" + imageIcon.isHidden = ActorSDK.sharedActor().messenger.getSelectedWallpaper() == "local:bg_\(index + 1).jpg" } override func layoutSubviews() { super.layoutSubviews() imageView.frame = contentView.bounds - imageIcon.frame = CGRectMake(contentView.width - 32, contentView.height - 32, 26, 26) + imageIcon.frame = CGRect(x: contentView.width - 32, y: contentView.height - 32, width: 26, height: 26) } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Settings/Cells/AAWallpapperSettingsCell.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Settings/Cells/AAWallpapperSettingsCell.swift index 920bb797db..55ab87f8b0 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Settings/Cells/AAWallpapperSettingsCell.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Settings/Cells/AAWallpapperSettingsCell.swift @@ -5,41 +5,41 @@ import Foundation import YYImage -public class AAWallpapperSettingsCell: AATableViewCell { +open class AAWallpapperSettingsCell: AATableViewCell { - private let wallpapper1 = UIImageView() - private let wallpapper1Icon = UIImageView() - private let wallpapper2 = UIImageView() - private let wallpapper2Icon = UIImageView() - private let wallpapper3 = UIImageView() - private let wallpapper3Icon = UIImageView() - private let label = UILabel() - private let disclose = UIImageView() + fileprivate let wallpapper1 = UIImageView() + fileprivate let wallpapper1Icon = UIImageView() + fileprivate let wallpapper2 = UIImageView() + fileprivate let wallpapper2Icon = UIImageView() + fileprivate let wallpapper3 = UIImageView() + fileprivate let wallpapper3Icon = UIImageView() + fileprivate let label = UILabel() + fileprivate let disclose = UIImageView() - public var wallpapperDidTap: ((name: String) -> ())? + open var wallpapperDidTap: ((_ name: String) -> ())? public override init(style: UITableViewCellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) wallpapper1.clipsToBounds = true - wallpapper1.contentMode = .ScaleAspectFill + wallpapper1.contentMode = .scaleAspectFill wallpapper1.image = UIImage.bundled("bg_1_preview.jpg")! - wallpapper1.viewDidTap = { [unowned self] () -> () in self.wallpapperDidTap?(name: "bg_1.jpg") } + wallpapper1.viewDidTap = { [unowned self] () -> () in self.wallpapperDidTap?("bg_1.jpg") } wallpapper1Icon.image = UIImage.bundled("ImageSelectedOn") wallpapper2.clipsToBounds = true - wallpapper2.contentMode = .ScaleAspectFill + wallpapper2.contentMode = .scaleAspectFill wallpapper2.image = UIImage.bundled("bg_2_preview.jpg")! - wallpapper2.viewDidTap = { [unowned self] () -> () in self.wallpapperDidTap?(name: "bg_2.jpg") } + wallpapper2.viewDidTap = { [unowned self] () -> () in self.wallpapperDidTap?("bg_2.jpg") } wallpapper2Icon.image = UIImage.bundled("ImageSelectedOn") wallpapper3.clipsToBounds = true - wallpapper3.contentMode = .ScaleAspectFill + wallpapper3.contentMode = .scaleAspectFill wallpapper3.image = UIImage.bundled("bg_3_preview.jpg")! - wallpapper3.viewDidTap = { [unowned self] () -> () in self.wallpapperDidTap?(name: "bg_3.jpg") } + wallpapper3.viewDidTap = { [unowned self] () -> () in self.wallpapperDidTap?("bg_3.jpg") } wallpapper3Icon.image = UIImage.bundled("ImageSelectedOn") - label.font = UIFont.systemFontOfSize(17) + label.font = UIFont.systemFont(ofSize: 17) label.textColor = appStyle.cellTextColor label.text = AALocalized("SettingsWallpapers") disclose.image = UIImage.bundled("ios_disclose") @@ -53,7 +53,7 @@ public class AAWallpapperSettingsCell: AATableViewCell { self.contentView.addSubview(label) self.contentView.addSubview(disclose) - disclose.hidden = false + disclose.isHidden = false //selectionStyle = .None } @@ -61,7 +61,7 @@ public class AAWallpapperSettingsCell: AATableViewCell { fatalError("init(coder:) has not been implemented") } - public override func layoutSubviews() { + open override func layoutSubviews() { super.layoutSubviews() let width = contentView.width @@ -71,22 +71,22 @@ public class AAWallpapperSettingsCell: AATableViewCell { let wWidth = (width - padding * 4) / 3 let wHeight = height - padding - 44 - wallpapper1.frame = CGRectMake(padding, wPadding, wWidth, wHeight) - wallpapper2.frame = CGRectMake(padding * 2 + wWidth, wPadding, wWidth, wHeight) - wallpapper3.frame = CGRectMake(padding * 3 + wWidth * 2, wPadding, wWidth, wHeight) + wallpapper1.frame = CGRect(x: padding, y: wPadding, width: wWidth, height: wHeight) + wallpapper2.frame = CGRect(x: padding * 2 + wWidth, y: wPadding, width: wWidth, height: wHeight) + wallpapper3.frame = CGRect(x: padding * 3 + wWidth * 2, y: wPadding, width: wWidth, height: wHeight) - wallpapper1Icon.frame = CGRectMake(wWidth - 32, wHeight - 32, 26, 26) - wallpapper2Icon.frame = CGRectMake(wWidth - 32, wHeight - 32, 26, 26) - wallpapper3Icon.frame = CGRectMake(wWidth - 32, wHeight - 32, 26, 26) + wallpapper1Icon.frame = CGRect(x: wWidth - 32, y: wHeight - 32, width: 26, height: 26) + wallpapper2Icon.frame = CGRect(x: wWidth - 32, y: wHeight - 32, width: 26, height: 26) + wallpapper3Icon.frame = CGRect(x: wWidth - 32, y: wHeight - 32, width: 26, height: 26) - label.frame = CGRectMake(padding, 0, width - padding * 2, 44) + label.frame = CGRect(x: padding, y: 0, width: width - padding * 2, height: 44) - disclose.frame = CGRectMake(width - 13 - 10, 15, 13, 14) + disclose.frame = CGRect(x: width - 13 - 10, y: 15, width: 13, height: 14) } - public func bind() { - wallpapper1Icon.hidden = ActorSDK.sharedActor().messenger.getSelectedWallpaper() != "local:bg_1.jpg" - wallpapper2Icon.hidden = ActorSDK.sharedActor().messenger.getSelectedWallpaper() != "local:bg_2.jpg" - wallpapper3Icon.hidden = ActorSDK.sharedActor().messenger.getSelectedWallpaper() != "local:bg_3.jpg" + open func bind() { + wallpapper1Icon.isHidden = ActorSDK.sharedActor().messenger.getSelectedWallpaper() != "local:bg_1.jpg" + wallpapper2Icon.isHidden = ActorSDK.sharedActor().messenger.getSelectedWallpaper() != "local:bg_2.jpg" + wallpapper3Icon.isHidden = ActorSDK.sharedActor().messenger.getSelectedWallpaper() != "local:bg_3.jpg" } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/User/AAUserViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/User/AAUserViewController.swift index d322040fd1..2f72a2b708 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/User/AAUserViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/User/AAUserViewController.swift @@ -10,7 +10,7 @@ class AAUserViewController: AAContentTableController { var isContactRow: AACommonRow! init(uid: Int) { - super.init(style: AAContentTableStyle.SettingsPlain) + super.init(style: AAContentTableStyle.settingsPlain) self.uid = uid self.autoTrack = true @@ -36,11 +36,11 @@ class AAUserViewController: AAContentTableController { r.avatar = self.user.getAvatarModel().get() let presence = self.user.getPresenceModel().get() - let presenceText = Actor.getFormatter().formatPresence(presence, withSex: self.user.getSex()) + let presenceText = Actor.getFormatter().formatPresence(presence, with: self.user.getSex()) if !self.isBot { r.subtitle = presenceText - if presence!.state.ordinal() == ACUserPresence_State.ONLINE().ordinal() { + if presence!.state.ordinal() == ACUserPresence_State.online().ordinal() { r.subtitleColor = self.appStyle.userOnlineColor } else { r.subtitleColor = self.appStyle.userOfflineColor @@ -53,13 +53,13 @@ class AAUserViewController: AAContentTableController { r.avatarDidTap = { [unowned self] (view: UIView) -> () in let avatar = self.user.getAvatarModel().get() - if avatar != nil && avatar.fullImage != nil { + if avatar != nil && avatar?.fullImage != nil { - let full = avatar.fullImage.fileReference - let small = avatar.smallImage.fileReference - let size = CGSize(width: Int(avatar.fullImage.width), height: Int(avatar.fullImage.height)) + let full = avatar?.fullImage.fileReference + let small = avatar?.smallImage.fileReference + let size = CGSize(width: Int((avatar?.fullImage.width)!), height: Int((avatar?.fullImage.height)!)) - self.presentViewController(AAPhotoPreviewController(file: full, previewFile: small, size: size, fromView: view), animated: true, completion: nil) + self.present(AAPhotoPreviewController(file: full!, previewFile: small, size: size, fromView: view), animated: true, completion: nil) } } } @@ -68,7 +68,7 @@ class AAUserViewController: AAContentTableController { // Profile: Starting Voice Call s.action("CallsStartAudio") { (r) -> () in r.selectAction = { () -> Bool in - self.execute(Actor.doCallWithUid(jint(self.uid))) + self.execute(Actor.doCall(withUid: jint(self.uid))) return false } } @@ -77,12 +77,12 @@ class AAUserViewController: AAContentTableController { // Profile: Send messages s.action("ProfileSendMessage") { (r) -> () in r.selectAction = { () -> Bool in - if let customController = ActorSDK.sharedActor().delegate.actorControllerForConversation(ACPeer.userWithInt(jint(self.uid))) { + if let customController = ActorSDK.sharedActor().delegate.actorControllerForConversation(ACPeer.user(with: jint(self.uid))) { self.navigateDetail(customController) } else { - self.navigateDetail(ConversationViewController(peer: ACPeer.userWithInt(jint(self.uid)))) + self.navigateDetail(ConversationViewController(peer: ACPeer.user(with: jint(self.uid)))) } - self.popover?.dismissPopoverAnimated(true) + self.popover?.dismiss(animated: true) return false } } @@ -113,9 +113,9 @@ class AAUserViewController: AAContentTableController { } r.selectAction = { (c: ACUserPhone) -> Bool in let phoneNumber = c.phone - let hasPhone = UIApplication.sharedApplication().canOpenURL(NSURL(string: "telprompt://")!) + let hasPhone = UIApplication.shared.canOpenURL(URL(string: "telprompt://")!) if (!hasPhone) { - UIPasteboard.generalPasteboard().string = "+\(phoneNumber)" + UIPasteboard.general.string = "+\(phoneNumber)" self.alertUser("NumberCopied") } else { ActorSDK.sharedActor().openUrl("telprompt://+\(phoneNumber)") @@ -149,44 +149,44 @@ class AAUserViewController: AAContentTableController { section { (s) -> () in s.common { (r) -> () in - let peer = ACPeer.userWithInt(jint(self.uid)) - r.style = .Switch + let peer = ACPeer.user(with: jint(self.uid)) + r.style = .switch r.content = AALocalized("ProfileNotifications") r.bindAction = { (r) -> () in - r.switchOn = Actor.isNotificationsEnabledWithPeer(peer) + r.switchOn = Actor.isNotificationsEnabled(with: peer) } r.switchAction = { (on: Bool) -> () in - if !on && !self.user.isBot().boolValue { + if !on && !self.user.isBot() { self.confirmAlertUser("ProfileNotificationsWarring", action: "ProfileNotificationsWarringAction", tapYes: { () -> () in - Actor.changeNotificationsEnabledWithPeer(peer, withValue: false) + Actor.changeNotificationsEnabled(with: peer, withValue: false) }, tapNo: { () -> () in r.reload() }) return } - Actor.changeNotificationsEnabledWithPeer(peer, withValue: on) + Actor.changeNotificationsEnabled(with: peer, withValue: on) } if(ActorSDK.sharedActor().enableChatGroupSound) { - if(Actor.isNotificationsEnabledWithPeer(peer)){ + if(Actor.isNotificationsEnabled(with: peer)){ r.selectAction = {() -> Bool in // Sound: Choose sound let setRingtoneController = AARingtonesViewController() - let sound = Actor.getNotificationsSoundWithPeer(peer) - setRingtoneController.selectedRingtone = (sound != nil) ? sound : "" + let sound = Actor.getNotificationsSound(with: peer) + setRingtoneController.selectedRingtone = (sound != nil) ? sound! : "" setRingtoneController.completion = {(selectedSound:String) in - Actor.changeNotificationsSoundPeer(peer, withValue: selectedSound) + Actor.changeNotificationsSound(peer, withValue: selectedSound) } let navigationController = AANavigationController(rootViewController: setRingtoneController) if (AADevice.isiPad) { - navigationController.modalInPopover = true - navigationController.modalPresentationStyle = UIModalPresentationStyle.CurrentContext + navigationController.isModalInPopover = true + navigationController.modalPresentationStyle = UIModalPresentationStyle.currentContext } - self.presentViewController(navigationController, animated: true, completion: { + self.present(navigationController, animated: true, completion: { } ) return false @@ -204,18 +204,18 @@ class AAUserViewController: AAContentTableController { r.bindAction = { (r) -> () in if self.user.isContactModel().get().booleanValue() { r.content = AALocalized("ProfileRemoveFromContacts") - r.style = .Destructive + r.style = .destructive } else { r.content = AALocalized("ProfileAddToContacts") - r.style = .Action + r.style = .action } } r.selectAction = { () -> Bool in if (self.user.isContactModel().get().booleanValue()) { - self.execute(Actor.removeContactCommandWithUid(jint(self.uid))!) + self.execute(Actor.removeContactCommand(withUid: jint(self.uid))!) } else { - self.execute(Actor.addContactCommandWithUid(jint(self.uid))!) + self.execute(Actor.addContactCommand(withUid: jint(self.uid))!) } return true } @@ -236,7 +236,7 @@ class AAUserViewController: AAContentTableController { if d.length == 0 { return } - c.executeSafeOnlySuccess(Actor.editNameCommandWithUid(jint(self.uid), withName: d)!, successBlock: { (val) -> Void in + c.executeSafeOnlySuccess(Actor.editNameCommand(withUid: jint(self.uid), withName: d)!, successBlock: { (val) -> Void in c.dismiss() }) } @@ -267,21 +267,21 @@ class AAUserViewController: AAContentTableController { } else { r.content = AALocalized("ProfileUnblockContact") } - r.style = .Destructive + r.style = .destructive } r.selectAction = { () -> Bool in if !self.user.isBlockedModel().get().booleanValue() { self.executePromise(Actor.blockUser(jint(self.uid)), successBlock: { success in - dispatch_async(dispatch_get_main_queue(),{ + DispatchQueue.main.async(execute: { r.reload() }) } ,failureBlock:nil) } else { self.executePromise(Actor.unblockUser(jint(self.uid)), successBlock: { success in - dispatch_async(dispatch_get_main_queue(),{ + DispatchQueue.main.async(execute: { r.reload() }) } ,failureBlock:nil) @@ -293,7 +293,7 @@ class AAUserViewController: AAContentTableController { } } - override func tableWillBind(binder: AABinder) { + override func tableWillBind(_ binder: AABinder) { binder.bind(user.getAvatarModel(), closure: { (value: ACAvatar?) -> () in self.headerRow.reload() }) diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ElegantPresentationController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ElegantPresentationController.swift index 1f2c5b4b1f..c73dfe4ca7 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ElegantPresentationController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ElegantPresentationController.swift @@ -8,7 +8,7 @@ import UIKit -typealias CoordinatedAnimation = UIViewControllerTransitionCoordinatorContext? -> Void +typealias CoordinatedAnimation = (UIViewControllerTransitionCoordinatorContext?) -> Void class ElegantPresentationController: UIPresentationController { @@ -16,22 +16,22 @@ class ElegantPresentationController: UIPresentationController { // MARK: - Properties /// Dims the presenting view controller, if option is set - private lazy var dimmingView: UIView = { + fileprivate lazy var dimmingView: UIView = { let view = UIView() - view.backgroundColor = UIColor.blackColor().colorWithAlphaComponent(0.5) + view.backgroundColor = UIColor.black.withAlphaComponent(0.5) view.alpha = 0 - view.userInteractionEnabled = false + view.isUserInteractionEnabled = false return view }() /// For dismissing on tap if option is set - private lazy var recognizer: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: Selector("dismiss:")) + fileprivate lazy var recognizer: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(ElegantPresentationController.dismiss(_:))) /// An options struct containing the customization options set - private let options: PresentationOptions + fileprivate let options: PresentationOptions // MARK: - Lifecycle @@ -47,7 +47,7 @@ class ElegantPresentationController: UIPresentationController { */ init(presentedViewController: UIViewController, presentingViewController: UIViewController, options: PresentationOptions) { self.options = options - super.init(presentedViewController: presentedViewController, presentingViewController: presentingViewController) + super.init(presentedViewController: presentedViewController, presenting: presentingViewController) } @@ -63,7 +63,7 @@ class ElegantPresentationController: UIPresentationController { // Prepare and position the dimming view dimmingView.alpha = 0 dimmingView.frame = containerView!.bounds - containerView?.insertSubview(dimmingView, atIndex: 0) + containerView?.insertSubview(dimmingView, at: 0) // Animate these properties with the transtion coordinator if possible let animations: CoordinatedAnimation = { [unowned self] _ in @@ -79,7 +79,7 @@ class ElegantPresentationController: UIPresentationController { // Animate these properties with the transtion coordinator if possible let animations: CoordinatedAnimation = { [unowned self] _ in self.dimmingView.alpha = 0 - self.presentingViewController.view.transform = CGAffineTransformIdentity + self.presentingViewController.view.transform = CGAffineTransform.identity } transtionWithCoordinator(animations) @@ -88,7 +88,7 @@ class ElegantPresentationController: UIPresentationController { // MARK: - Adaptation - override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) { + override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { /* There's a bug when rotating that makes the presented view controller permanently @@ -97,19 +97,19 @@ class ElegantPresentationController: UIPresentationController { It jumps because it's not in the animation block, but isn't noticiable unless in slow-mo. Placing it in the animation block does not fix the issue, so here it is. */ - presentingViewController.view.transform = CGAffineTransformIdentity + presentingViewController.view.transform = CGAffineTransform.identity // Animate these with the coordinator let animations: CoordinatedAnimation = { [unowned self] _ in self.dimmingView.frame = self.containerView!.bounds self.presentingViewController.view.transform = self.options.presentingTransform - self.presentedView()?.frame = self.frameOfPresentedViewInContainerView() + self.presentedView?.frame = self.frameOfPresentedViewInContainerView } - coordinator.animateAlongsideTransition(animations, completion: nil) + coordinator.animate(alongsideTransition: animations, completion: nil) } - override func sizeForChildContentContainer(container: UIContentContainer, withParentContainerSize parentSize: CGSize) -> CGSize { + override func size(forChildContentContainer container: UIContentContainer, withParentContainerSize parentSize: CGSize) -> CGSize { // Percent height doesn't make sense as a negative value or greater than zero, so we'll enforce it let percentHeight = min(abs(options.presentedPercentHeight), 1) @@ -125,11 +125,11 @@ class ElegantPresentationController: UIPresentationController { return parentSize } - override func frameOfPresentedViewInContainerView() -> CGRect { + override var frameOfPresentedViewInContainerView : CGRect { // Grab the parent and child sizes let parentSize = containerView!.bounds.size - let childSize = sizeForChildContentContainer(presentedViewController, withParentContainerSize: parentSize) + let childSize = size(forChildContentContainer: presentedViewController, withParentContainerSize: parentSize) // Create and return an appropiate frame return CGRect(x: 0, y: parentSize.height - childSize.height, width: childSize.width, height: childSize.height) @@ -139,20 +139,20 @@ class ElegantPresentationController: UIPresentationController { // MARK: - Helper functions // For the tap-to-dismiss - func dismiss(sender: UITapGestureRecognizer) { - presentedViewController.dismissViewControllerAnimated(true, completion: nil) + func dismiss(_ sender: UITapGestureRecognizer) { + presentedViewController.dismiss(animated: true, completion: nil) } /* I noticed myself doing this a lot (more so in earlier versions) so I made a quick function. Simply takes a closure with animations in them and attempts to animate with the coordinator. */ - private func transtionWithCoordinator(animations: CoordinatedAnimation) { - if let coordinator = presentingViewController.transitionCoordinator() { - coordinator.animateAlongsideTransition(animations, completion: nil) + fileprivate func transtionWithCoordinator(_ animations: @escaping CoordinatedAnimation) { + if let coordinator = presentingViewController.transitionCoordinator { + coordinator.animate(alongsideTransition: animations, completion: nil) } else { animations(nil) } } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ElegantPresentations.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ElegantPresentations.swift index 03b61965e6..2e70d505ab 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ElegantPresentations.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ElegantPresentations.swift @@ -37,7 +37,7 @@ public struct ElegantPresentations { struct PresentationOptions { var dimmingViewAlpha: CGFloat = 1 var dimmingViewTapDismisses = false - var presentingTransform = CGAffineTransformMakeScale(0.93, 0.93) + var presentingTransform = CGAffineTransform(scaleX: 0.93, y: 0.93) var presentedHeight: CGFloat = -1 var presentedPercentHeight = 1.0 var usePercentHeight = true @@ -47,18 +47,18 @@ struct PresentationOptions { init(options: Set) { for option in options { switch option { - case .NoDimmingView: dimmingViewAlpha = 0 - case .CustomDimmingViewAlpha(let alpha): dimmingViewAlpha = alpha - case .DismissOnDimmingViewTap: dimmingViewTapDismisses = true - case .PresentingViewKeepsSize: presentingTransform = CGAffineTransformIdentity - case .PresentedHeight(let height): + case .noDimmingView: dimmingViewAlpha = 0 + case .customDimmingViewAlpha(let alpha): dimmingViewAlpha = alpha + case .dismissOnDimmingViewTap: dimmingViewTapDismisses = true + case .presentingViewKeepsSize: presentingTransform = CGAffineTransform.identity + case .presentedHeight(let height): usePercentHeight = false presentedHeight = height - case .PresentedPercentHeight(let percentHeight): + case .presentedPercentHeight(let percentHeight): usePercentHeight = true presentedPercentHeight = percentHeight - case .CustomPresentingScale(let scale): - presentingTransform = CGAffineTransformMakeScale(CGFloat(min(1, scale)), CGFloat(min(1, scale))) + case .customPresentingScale(let scale): + presentingTransform = CGAffineTransform(scaleX: CGFloat(min(1, scale)), y: CGFloat(min(1, scale))) } } @@ -67,7 +67,7 @@ struct PresentationOptions { NSLog("\n-------------------------\nElegant Presentation Warning:\nDO NOT set a height and a percent height! Only one will be respected.\n-------------------------") } - if options.contains(.NoDimmingView) && dimmingViewAlpha != 0 { + if options.contains(.noDimmingView) && dimmingViewAlpha != 0 { NSLog("\n-------------------------\nElegant Presentation Warning:\nDO NOT set no dimming view and a custom dimming view alpha! Only one will be respected.\n-------------------------") } } @@ -84,23 +84,23 @@ struct PresentationOptions { */ public enum PresentationOption: Hashable { - case NoDimmingView - case CustomDimmingViewAlpha(CGFloat) - case DismissOnDimmingViewTap - case PresentingViewKeepsSize - case PresentedHeight(CGFloat) - case PresentedPercentHeight(Double) - case CustomPresentingScale(Double) + case noDimmingView + case customDimmingViewAlpha(CGFloat) + case dismissOnDimmingViewTap + case presentingViewKeepsSize + case presentedHeight(CGFloat) + case presentedPercentHeight(Double) + case customPresentingScale(Double) var description: String { switch self { - case .NoDimmingView: return "No dimming view" - case .CustomDimmingViewAlpha(let alpha): return "Custom dimming view alpha \(alpha)" - case .DismissOnDimmingViewTap: return "Dismiss on dimming view tap" - case .PresentingViewKeepsSize: return "Presenting view keeps size" - case .PresentedHeight(let height): return "Presented height \(height)" - case .PresentedPercentHeight(let percent): return "Presented percent height \(percent)" - case .CustomPresentingScale(let scale): return "Custom presenting scale \(scale)" + case .noDimmingView: return "No dimming view" + case .customDimmingViewAlpha(let alpha): return "Custom dimming view alpha \(alpha)" + case .dismissOnDimmingViewTap: return "Dismiss on dimming view tap" + case .presentingViewKeepsSize: return "Presenting view keeps size" + case .presentedHeight(let height): return "Presented height \(height)" + case .presentedPercentHeight(let percent): return "Presented percent height \(percent)" + case .customPresentingScale(let scale): return "Custom presenting scale \(scale)" } } @@ -111,4 +111,4 @@ public enum PresentationOption: Hashable { public func ==(lhs: PresentationOption, rhs: PresentationOption) -> Bool { return lhs.hashValue == rhs.hashValue -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/SwiftExtensions/AADevice.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/SwiftExtensions/AADevice.swift index 5e20b9e8a8..31571806a7 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/SwiftExtensions/AADevice.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/SwiftExtensions/AADevice.swift @@ -10,20 +10,20 @@ public struct AADevice { // // Device Types // - public static let isiPad = UIDevice.currentDevice().userInterfaceIdiom == .Pad - public static let isiPhone = UIDevice.currentDevice().userInterfaceIdiom == .Phone + public static let isiPad = UIDevice.current.userInterfaceIdiom == .pad + public static let isiPhone = UIDevice.current.userInterfaceIdiom == .phone // // OS Versions // public static let isiOS8 = true - public static let isiOS9 = NSProcessInfo.processInfo().isOperatingSystemAtLeastVersion( NSOperatingSystemVersion(majorVersion: 9, minorVersion: 0, patchVersion: 0)) + public static let isiOS9 = ProcessInfo.processInfo.isOperatingSystemAtLeast( OperatingSystemVersion(majorVersion: 9, minorVersion: 0, patchVersion: 0)) // // Device Sizes // - public static let screenWidth = min(UIScreen.mainScreen().bounds.size.width, UIScreen.mainScreen().bounds.size.height) - public static let screenHeight = max(UIScreen.mainScreen().bounds.size.width, UIScreen.mainScreen().bounds.size.height) + public static let screenWidth = min(UIScreen.main.bounds.size.width, UIScreen.main.bounds.size.height) + public static let screenHeight = max(UIScreen.main.bounds.size.width, UIScreen.main.bounds.size.height) // // iPhone sizes @@ -32,4 +32,4 @@ public struct AADevice { public static let isiPhone5 = isiPhone && screenHeight == 568.0 public static let isiPhone6 = isiPhone && screenHeight == 667.0 public static let isiPhone6P = isiPhone && screenHeight == 736.0 -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/SwiftExtensions/AALocalized.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/SwiftExtensions/AALocalized.swift index 34dd55f330..6cd41df358 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/SwiftExtensions/AALocalized.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/SwiftExtensions/AALocalized.swift @@ -6,7 +6,7 @@ import Foundation // Shorter helper for localized strings -public func AALocalized(text: String!) -> String! { +public func AALocalized(_ text: String!) -> String! { if text == nil { return nil } @@ -24,12 +24,12 @@ public func AALocalized(text: String!) -> String! { } } - return NSLocalizedString(text, tableName: nil, bundle: NSBundle.framework, value: text, comment: "") + return NSLocalizedString(text, tableName: nil, bundle: Bundle.framework, value: text, comment: "") } // Registration localization table -public func AARegisterLocalizedBundle(table: String, bundle: NSBundle) { +public func AARegisterLocalizedBundle(_ table: String, bundle: Bundle) { tables.append(LocTable(table: table, bundle: bundle)) } @@ -38,9 +38,9 @@ private var tables = [LocTable]() private class LocTable { let table: String - let bundle: NSBundle + let bundle: Bundle - init(table: String, bundle: NSBundle) { + init(table: String, bundle: Bundle) { self.table = table self.bundle = bundle } @@ -64,4 +64,4 @@ public extension UILabel { } } } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/SwiftExtensions/AARegex.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/SwiftExtensions/AARegex.swift index fe7ba087ff..23443bccd1 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/SwiftExtensions/AARegex.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/SwiftExtensions/AARegex.swift @@ -12,14 +12,14 @@ class AARegex { init(_ pattern: String) { self.pattern = pattern do { - self.internalExpression = try NSRegularExpression(pattern: pattern, options: .CaseInsensitive) + self.internalExpression = try NSRegularExpression(pattern: pattern, options: .caseInsensitive) } catch { fatalError("Incorrect regex: \(pattern)") } } - func test(input: String) -> Bool { - let matches = self.internalExpression.matchesInString(input, options: NSMatchingOptions(), range:NSMakeRange(0, input.length)) + func test(_ input: String) -> Bool { + let matches = self.internalExpression.matches(in: input, options: NSRegularExpression.MatchingOptions(), range:NSMakeRange(0, input.length)) return matches.count > 0 } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/SwiftExtensions/AssosiatedObject.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/SwiftExtensions/AssosiatedObject.swift index 2d31e8ce21..a4eaf78f9b 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/SwiftExtensions/AssosiatedObject.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/SwiftExtensions/AssosiatedObject.swift @@ -5,7 +5,7 @@ import Foundation import ObjectiveC -public func setAssociatedObject(object: AnyObject, value: T, associativeKey: UnsafePointer, policy: objc_AssociationPolicy = objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) { +public func setAssociatedObject(_ object: AnyObject, value: T, associativeKey: UnsafeRawPointer, policy: objc_AssociationPolicy = objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) { if let v: AnyObject = value as? AnyObject { objc_setAssociatedObject(object, associativeKey, v, policy) } @@ -14,7 +14,7 @@ public func setAssociatedObject(object: AnyObject, value: T, associativeKey: } } -public func getAssociatedObject(object: AnyObject, associativeKey: UnsafePointer) -> T? { +public func getAssociatedObject(_ object: AnyObject, associativeKey: UnsafeRawPointer) -> T? { if let v = objc_getAssociatedObject(object, associativeKey) as? T { return v } @@ -33,6 +33,6 @@ final class Lifted { } } -private func lift(x: T) -> Lifted { +private func lift(_ x: T) -> Lifted { return Lifted(x) -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/SwiftExtensions/Collections.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/SwiftExtensions/Collections.swift index 934286db48..5792e7bda6 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/SwiftExtensions/Collections.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/SwiftExtensions/Collections.swift @@ -5,7 +5,7 @@ import Foundation public extension Array { - public func contains(obj: T) -> Bool { + public func contains(_ obj: T) -> Bool where T : Equatable { return self.filter({$0 as? T == obj}).count > 0 } } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/SwiftExtensions/Colors.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/SwiftExtensions/Colors.swift index 11ad585987..981c8dae6c 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/SwiftExtensions/Colors.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/SwiftExtensions/Colors.swift @@ -6,12 +6,12 @@ import UIKit public extension UIColor { - public convenience init(rgb: UInt) { - self.init(red: CGFloat((rgb & 0xFF0000) >> 16) / 255.0, - green: CGFloat((rgb & 0x00FF00) >> 8) / 255.0, - blue: CGFloat(rgb & 0x0000FF) / 255.0, - alpha: CGFloat(1.0)) - } +// public convenience init(rgb: UInt) { +// self.init(red: CGFloat((rgb & 0xFF0000) >> 16) / 255.0, +// green: CGFloat((rgb & 0x00FF00) >> 8) / 255.0, +// blue: CGFloat(rgb & 0x0000FF) / 255.0, +// alpha: CGFloat(1.0)) +// } public convenience init(rgb: UInt, alpha: Double) { self.init(red: CGFloat((rgb & 0xFF0000) >> 16) / 255.0, @@ -27,15 +27,15 @@ public extension UIColor { alpha: CGFloat(1.0)) } - public class func alphaBlack(alpha: Double) -> UIColor { + public class func alphaBlack(_ alpha: Double) -> UIColor { return UIColor(red: 0, green: 0, blue: 0, alpha: CGFloat(alpha)) } - public class func alphaWhite(alpha: Double) -> UIColor { + public class func alphaWhite(_ alpha: Double) -> UIColor { return UIColor(red: 1, green: 1, blue: 1, alpha: CGFloat(alpha)) } - public func alpha(alpha: Double) -> UIColor { + public func alpha(_ alpha: Double) -> UIColor { var r:CGFloat = 0 var g:CGFloat = 0 var b:CGFloat = 0 diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/SwiftExtensions/Dispatch.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/SwiftExtensions/Dispatch.swift index 59f3a65b8a..d005943fbc 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/SwiftExtensions/Dispatch.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/SwiftExtensions/Dispatch.swift @@ -4,36 +4,36 @@ import Foundation -private let backgroundQueue = dispatch_queue_create("im.actor.background", DISPATCH_QUEUE_SERIAL) +private let backgroundQueue = DispatchQueue(label: "im.actor.background", attributes: []) -public func dispatchOnUi(closure: () -> Void) { - dispatch_async(dispatch_get_main_queue(), { () -> Void in +public func dispatchOnUi(_ closure: @escaping () -> Void) { + DispatchQueue.main.async(execute: { () -> Void in closure() }) } -public func dispatchOnUiSync(closure: () -> Void) { - dispatch_sync(dispatch_get_main_queue(), { () -> Void in +public func dispatchOnUiSync(_ closure: () -> Void) { + DispatchQueue.main.sync(execute: { () -> Void in closure() }) } -public func dispatchAfterOnUi(delay: Double, closure: () -> Void) { - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(delay * Double(NSEC_PER_SEC))), dispatch_get_main_queue()) { () -> Void in +public func dispatchAfterOnUi(_ delay: Double, closure: @escaping () -> Void) { + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Double(Int64(delay * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC)) { () -> Void in closure() } } -public func dispatchBackground(closure: () -> Void) { - dispatch_async(backgroundQueue) { +public func dispatchBackground(_ closure: @escaping () -> Void) { + backgroundQueue.async { closure() } } -public func dispatchBackgroundDelayed(delay: Double, closure: () -> Void) { - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(delay * Double(NSEC_PER_SEC))), backgroundQueue) { +public func dispatchBackgroundDelayed(_ delay: Double, closure: @escaping () -> Void) { + backgroundQueue.asyncAfter(deadline: DispatchTime.now() + Double(Int64(delay * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC)) { closure() } } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/SwiftExtensions/Fonts.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/SwiftExtensions/Fonts.swift index d5874755df..669eaf7ae7 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/SwiftExtensions/Fonts.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/SwiftExtensions/Fonts.swift @@ -6,25 +6,25 @@ import Foundation public extension UIFont { - public class func thinSystemFontOfSize(size: CGFloat) -> UIFont { + public class func thinSystemFontOfSize(_ size: CGFloat) -> UIFont { if #available(iOS 8.2, *) { - return UIFont.systemFontOfSize(size, weight: UIFontWeightThin) + return UIFont.systemFont(ofSize: size, weight: UIFontWeightThin) } else { return UIFont(name: "HelveticaNeue-Thin", size: size)! } } - public class func lightSystemFontOfSize(size: CGFloat) -> UIFont { + public class func lightSystemFontOfSize(_ size: CGFloat) -> UIFont { if #available(iOS 8.2, *) { - return UIFont.systemFontOfSize(size, weight: UIFontWeightLight) + return UIFont.systemFont(ofSize: size, weight: UIFontWeightLight) } else { return UIFont(name: "HelveticaNeue-Light", size: size)! } } - public class func mediumSystemFontOfSize(size: CGFloat) -> UIFont { + public class func mediumSystemFontOfSize(_ size: CGFloat) -> UIFont { if #available(iOS 8.2, *) { - return UIFont.systemFontOfSize(size, weight: UIFontWeightMedium) + return UIFont.systemFont(ofSize: size, weight: UIFontWeightMedium) } else { return UIFont(name: "HelveticaNeue-Medium", size: size)! } @@ -32,15 +32,15 @@ public extension UIFont { // Texts - public class func textFontOfSize(size: CGFloat) -> UIFont { + public class func textFontOfSize(_ size: CGFloat) -> UIFont { return UIFont(name: "HelveticaNeue", size: size)! } - public class func italicTextFontOfSize(size: CGFloat) -> UIFont { + public class func italicTextFontOfSize(_ size: CGFloat) -> UIFont { return UIFont(name: "HelveticaNeue-Italic", size: size)! } - public class func boldTextFontOfSize(size: CGFloat) -> UIFont { + public class func boldTextFontOfSize(_ size: CGFloat) -> UIFont { return UIFont(name: "HelveticaNeue-Medium", size: size)! } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/SwiftExtensions/Images.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/SwiftExtensions/Images.swift index 757b4bb963..ed9ca3e8fc 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/SwiftExtensions/Images.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/SwiftExtensions/Images.swift @@ -9,18 +9,18 @@ import Accelerate public extension UIImage { - class func tinted(named: String, color: UIColor) -> UIImage { + class func tinted(_ named: String, color: UIColor) -> UIImage { return UIImage.bundled(named)!.tintImage(color) } - class func templated(named: String) -> UIImage { - return UIImage.bundled(named)!.imageWithRenderingMode(.AlwaysTemplate) + class func templated(_ named: String) -> UIImage { + return UIImage.bundled(named)!.withRenderingMode(.alwaysTemplate) } - public func tintImage(color:UIColor) -> UIImage{ - UIGraphicsBeginImageContextWithOptions(self.size,false,UIScreen.mainScreen().scale); + public func tintImage(_ color:UIColor) -> UIImage{ + UIGraphicsBeginImageContextWithOptions(self.size,false,UIScreen.main.scale); - var rect = CGRectZero; + var rect = CGRect.zero; rect.size = self.size; // Composite tint color at its own opacity. color.set(); @@ -28,22 +28,22 @@ public extension UIImage { // Mask tint color-swatch to this image's opaque mask. // We want behaviour like NSCompositeDestinationIn on Mac OS X. - self.drawInRect(rect, blendMode: .DestinationIn, alpha: 1.0) + self.draw(in: rect, blendMode: .destinationIn, alpha: 1.0) let image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); if (self.capInsets.bottom != 0 || self.capInsets.top != 0 || self.capInsets.left != 0 || self.capInsets.right != 0) { - return image.resizableImageWithCapInsets(capInsets, resizingMode: resizingMode) + return image!.resizableImage(withCapInsets: capInsets, resizingMode: resizingMode) } - return image.imageWithRenderingMode(UIImageRenderingMode.AlwaysOriginal) + return image!.withRenderingMode(UIImageRenderingMode.alwaysOriginal) } - public func tintBgImage(color: UIColor) -> UIImage { - UIGraphicsBeginImageContextWithOptions(self.size,false,UIScreen.mainScreen().scale); + public func tintBgImage(_ color: UIColor) -> UIImage { + UIGraphicsBeginImageContextWithOptions(self.size,false,UIScreen.main.scale); - var rect = CGRectZero; + var rect = CGRect.zero; rect.size = self.size; // Composite tint color at its own opacity. color.set(); @@ -51,133 +51,133 @@ public extension UIImage { // Mask tint color-swatch to this image's opaque mask. // We want behaviour like NSCompositeDestinationIn on Mac OS X. - self.drawInRect(rect, blendMode: .Overlay, alpha: 1.0) + self.draw(in: rect, blendMode: .overlay, alpha: 1.0) let image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); - return image + return image! } - public func roundImage(newSize: Int) -> UIImage { + public func roundImage(_ newSize: Int) -> UIImage { let nSize = CGSize(width: newSize, height: newSize) - UIGraphicsBeginImageContextWithOptions(nSize,false,UIScreen.mainScreen().scale); + UIGraphicsBeginImageContextWithOptions(nSize,false,UIScreen.main.scale); let context = UIGraphicsGetCurrentContext(); // Background - CGContextAddPath(context, CGPathCreateWithEllipseInRect(CGRect(origin: CGPointZero, size: nSize),nil)); - CGContextClip(context); - self.drawInRect(CGRect(origin: CGPointMake(-1, -1), size: CGSize(width: (newSize+2), height: (newSize+2)))); + context?.addPath(CGPath(ellipseIn: CGRect(origin: CGPoint.zero, size: nSize),transform: nil)); + context?.clip(); + self.draw(in: CGRect(origin: CGPoint(x: -1, y: -1), size: CGSize(width: (newSize+2), height: (newSize+2)))); // Border - CGContextSetStrokeColorWithColor(context, UIColor(red: 0, green: 0, blue: 0, alpha: 0x19/255.0).CGColor); - CGContextAddArc(context,CGFloat(newSize)/2, CGFloat(newSize)/2, CGFloat(newSize)/2, CGFloat(M_PI * 0), CGFloat(M_PI * 2), 0); - CGContextDrawPath(context, .Stroke); + context?.setStrokeColor(UIColor(red: 0, green: 0, blue: 0, alpha: 0x19/255.0).cgColor); + // context?.addArc(CGFloat(newSize)/2, CGFloat(newSize)/2, CGFloat(newSize)/2, CGFloat(M_PI * 0), CGFloat(M_PI * 2), 0); + context?.drawPath(using: .stroke); let image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); - return image; + return image!; } - public func roundCorners(w: CGFloat, h: CGFloat, roundSize: CGFloat) -> UIImage { + public func roundCorners(_ w: CGFloat, h: CGFloat, roundSize: CGFloat) -> UIImage { let nSize = CGSize(width: w, height: h) - UIGraphicsBeginImageContextWithOptions(nSize, false, UIScreen.mainScreen().scale); + UIGraphicsBeginImageContextWithOptions(nSize, false, UIScreen.main.scale); // Background - UIBezierPath(roundedRect: CGRectMake(0, 0, w, h), cornerRadius: roundSize).addClip() + UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: w, height: h), cornerRadius: roundSize).addClip() - self.drawInRect(CGRect(origin: CGPointMake(-1, -1), size: CGSize(width: (w+2), height: (h+2)))); + self.draw(in: CGRect(origin: CGPoint(x: -1, y: -1), size: CGSize(width: (w+2), height: (h+2)))); let image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); - return image; + return image!; } - public func resizeSquare(maxW: CGFloat, maxH: CGFloat) -> UIImage { + public func resizeSquare(_ maxW: CGFloat, maxH: CGFloat) -> UIImage { let realW = self.size.width / self.scale; let realH = self.size.height / self.scale; let factor = min(maxW/realW, maxH/realH) return resize(factor * realW, h: factor * realH) } - func resizeOptimize(maxPixels: Int) -> UIImage { + func resizeOptimize(_ maxPixels: Int) -> UIImage { let realW = self.size.width / self.scale; let realH = self.size.height / self.scale; let factor = min(1.0, CGFloat(maxPixels) / (realW * realH)); return resize(factor * realW, h: factor * realH) } - public func resize(w: CGFloat, h: CGFloat) -> UIImage { + public func resize(_ w: CGFloat, h: CGFloat) -> UIImage { let nSize = CGSize(width: w, height: h) UIGraphicsBeginImageContextWithOptions(nSize, false, 1.0) - drawInRect(CGRect(origin: CGPointMake(-1, -1), size: CGSize(width: (w + 2), height: (h + 2)))) + draw(in: CGRect(origin: CGPoint(x: -1, y: -1), size: CGSize(width: (w + 2), height: (h + 2)))) let image = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() - return image; + return image!; } - public func aa_imageWithColor(color1: UIColor) -> UIImage { + public func aa_imageWithColor(_ color1: UIColor) -> UIImage { UIGraphicsBeginImageContextWithOptions(self.size, false, self.scale) let context = UIGraphicsGetCurrentContext() - CGContextTranslateCTM(context, 0, self.size.height) - CGContextScaleCTM(context, 1.0, -1.0); - CGContextSetBlendMode(context, CGBlendMode.Normal) + context?.translateBy(x: 0, y: self.size.height) + context?.scaleBy(x: 1.0, y: -1.0); + context?.setBlendMode(CGBlendMode.normal) - let rect = CGRectMake(0, 0, self.size.width, self.size.height) as CGRect - CGContextClipToMask(context, rect, self.CGImage) + let rect = CGRect(x: 0, y: 0, width: self.size.width, height: self.size.height) as CGRect + context?.clip(to: rect, mask: self.cgImage!) color1.setFill() - CGContextFillRect(context, rect) + context?.fill(rect) - let newImage = UIGraphicsGetImageFromCurrentImageContext() as UIImage + let newImage = UIGraphicsGetImageFromCurrentImageContext()! as UIImage UIGraphicsEndImageContext() return newImage } } -public class Imaging { +open class Imaging { - public class func roundedImage(color: UIColor, radius: CGFloat) -> UIImage { - return roundedImage(color, size: CGSizeMake(radius * 2, radius * 2), radius: radius) + open class func roundedImage(_ color: UIColor, radius: CGFloat) -> UIImage { + return roundedImage(color, size: CGSize(width: radius * 2, height: radius * 2), radius: radius) } - public class func roundedImage(color: UIColor, size: CGSize, radius: CGFloat) -> UIImage { + open class func roundedImage(_ color: UIColor, size: CGSize, radius: CGFloat) -> UIImage { UIGraphicsBeginImageContextWithOptions(size, false, 0) - let path = UIBezierPath(roundedRect: CGRectMake(0, 0, size.width, size.height), cornerRadius: radius) + let path = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: size.width, height: size.height), cornerRadius: radius) path.lineWidth = 1 color.setFill() path.fill() - let image: UIImage = UIGraphicsGetImageFromCurrentImageContext() + let image: UIImage = UIGraphicsGetImageFromCurrentImageContext()! UIGraphicsEndImageContext() - return image.resizableImageWithCapInsets(UIEdgeInsetsMake(radius, radius, radius, radius)) + return image.resizableImage(withCapInsets: UIEdgeInsetsMake(radius, radius, radius, radius)) } - public class func circleImage(color: UIColor, radius: CGFloat) -> UIImage { - return circleImage(color, size: CGSizeMake(radius * 2, radius * 2), radius: radius) + open class func circleImage(_ color: UIColor, radius: CGFloat) -> UIImage { + return circleImage(color, size: CGSize(width: radius * 2, height: radius * 2), radius: radius) } - public class func circleImage(color: UIColor, size: CGSize, radius: CGFloat) -> UIImage { + open class func circleImage(_ color: UIColor, size: CGSize, radius: CGFloat) -> UIImage { UIGraphicsBeginImageContextWithOptions(size, false, 0) - let path = UIBezierPath(roundedRect: CGRectMake(1, 1, size.width - 2, size.height - 2), cornerRadius: radius) + let path = UIBezierPath(roundedRect: CGRect(x: 1, y: 1, width: size.width - 2, height: size.height - 2), cornerRadius: radius) color.setStroke() path.stroke() - let image: UIImage = UIGraphicsGetImageFromCurrentImageContext() + let image: UIImage = UIGraphicsGetImageFromCurrentImageContext()! UIGraphicsEndImageContext() - return image.resizableImageWithCapInsets(UIEdgeInsetsMake(radius, radius, radius, radius)) + return image.resizableImage(withCapInsets: UIEdgeInsetsMake(radius, radius, radius, radius)) } - public class func imageWithColor(color: UIColor, size: CGSize) -> UIImage { - let rect = CGRectMake(0, 0, size.width, size.height) + open class func imageWithColor(_ color: UIColor, size: CGSize) -> UIImage { + let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height) UIGraphicsBeginImageContextWithOptions(size, false, 0) color.setFill() UIRectFill(rect) - let image: UIImage = UIGraphicsGetImageFromCurrentImageContext() + let image: UIImage = UIGraphicsGetImageFromCurrentImageContext()! UIGraphicsEndImageContext() return image } @@ -198,11 +198,11 @@ public extension UIImage { return applyBlurWithRadius(20, tintColor: UIColor(white: 0.11, alpha: 0.73), saturationDeltaFactor: 1.8) } - public func applyTintEffectWithColor(tintColor: UIColor) -> UIImage? { + public func applyTintEffectWithColor(_ tintColor: UIColor) -> UIImage? { let effectColorAlpha: CGFloat = 0.6 var effectColor = tintColor - let componentCount = CGColorGetNumberOfComponents(tintColor.CGColor) + let componentCount = tintColor.cgColor.numberOfComponents if componentCount == 2 { var b: CGFloat = 0 @@ -222,39 +222,39 @@ public extension UIImage { return applyBlurWithRadius(10, tintColor: effectColor, saturationDeltaFactor: -1.0, maskImage: nil) } - public func applyBlur(blurRadius: CGFloat) -> UIImage? { + public func applyBlur(_ blurRadius: CGFloat) -> UIImage? { return applyBlurWithRadius(blurRadius, tintColor: nil, saturationDeltaFactor: 1.0) } - public func applyBlurWithRadius(blurRadius: CGFloat, tintColor: UIColor?, saturationDeltaFactor: CGFloat, maskImage: UIImage? = nil) -> UIImage? { + public func applyBlurWithRadius(_ blurRadius: CGFloat, tintColor: UIColor?, saturationDeltaFactor: CGFloat, maskImage: UIImage? = nil) -> UIImage? { // Check pre-conditions. if (size.width < 1 || size.height < 1) { print("*** error: invalid size: \(size.width) x \(size.height). Both dimensions must be >= 1: \(self)") return nil } - if self.CGImage == nil { + if self.cgImage == nil { print("*** error: image must be backed by a CGImage: \(self)") return nil } - if maskImage != nil && maskImage!.CGImage == nil { + if maskImage != nil && maskImage!.cgImage == nil { print("*** error: maskImage must be backed by a CGImage: \(maskImage)") return nil } let __FLT_EPSILON__ = CGFloat(FLT_EPSILON) - let screenScale = UIScreen.mainScreen().scale - let imageRect = CGRect(origin: CGPointZero, size: size) + let screenScale = UIScreen.main.scale + let imageRect = CGRect(origin: CGPoint.zero, size: size) var effectImage = self let hasBlur = blurRadius > __FLT_EPSILON__ let hasSaturationChange = fabs(saturationDeltaFactor - 1.0) > __FLT_EPSILON__ if hasBlur || hasSaturationChange { - func createEffectBuffer(context: CGContext) -> vImage_Buffer { - let data = CGBitmapContextGetData(context) - let width = vImagePixelCount(CGBitmapContextGetWidth(context)) - let height = vImagePixelCount(CGBitmapContextGetHeight(context)) - let rowBytes = CGBitmapContextGetBytesPerRow(context) + func createEffectBuffer(_ context: CGContext) -> vImage_Buffer { + let data = context.data + let width = vImagePixelCount(context.width) + let height = vImagePixelCount(context.height) + let rowBytes = context.bytesPerRow return vImage_Buffer(data: data, height: height, width: width, rowBytes: rowBytes) } @@ -262,9 +262,9 @@ public extension UIImage { UIGraphicsBeginImageContextWithOptions(size, false, screenScale) let effectInContext = UIGraphicsGetCurrentContext() - CGContextScaleCTM(effectInContext, 1.0, -1.0) - CGContextTranslateCTM(effectInContext, 0, -size.height) - CGContextDrawImage(effectInContext, imageRect, self.CGImage) + effectInContext?.scaleBy(x: 1.0, y: -1.0) + effectInContext?.translateBy(x: 0, y: -size.height) + effectInContext?.draw(self.cgImage!, in: imageRect) var effectInBuffer = createEffectBuffer(effectInContext!) @@ -290,7 +290,9 @@ public extension UIImage { // let inputRadius = blurRadius * screenScale - var radius = UInt32(floor(inputRadius * 3.0 * CGFloat(sqrt(2 * M_PI)) / 4 + 0.5)) + let radiusPi = CGFloat(sqrt(2 * M_PI)) + let radiusFl = floor(inputRadius * 3.0 * radiusPi / 4 + 0.5) + var radius = UInt32(radiusFl) if radius % 2 != 1 { radius += 1 // force radius to be odd so that the three box-blur methodology works. } @@ -315,7 +317,7 @@ public extension UIImage { let divisor: CGFloat = 256 let matrixSize = floatingPointSaturationMatrix.count - var saturationMatrix = [Int16](count: matrixSize, repeatedValue: 0) + var saturationMatrix = [Int16](repeating: 0, count: matrixSize) for i: Int in 0 ..< matrixSize { saturationMatrix[i] = Int16(round(floatingPointSaturationMatrix[i] * divisor)) @@ -330,13 +332,13 @@ public extension UIImage { } if !effectImageBuffersAreSwapped { - effectImage = UIGraphicsGetImageFromCurrentImageContext() + effectImage = UIGraphicsGetImageFromCurrentImageContext()! } UIGraphicsEndImageContext() if effectImageBuffersAreSwapped { - effectImage = UIGraphicsGetImageFromCurrentImageContext() + effectImage = UIGraphicsGetImageFromCurrentImageContext()! } UIGraphicsEndImageContext() @@ -345,28 +347,28 @@ public extension UIImage { // Set up output context. UIGraphicsBeginImageContextWithOptions(size, false, screenScale) let outputContext = UIGraphicsGetCurrentContext() - CGContextScaleCTM(outputContext, 1.0, -1.0) - CGContextTranslateCTM(outputContext, 0, -size.height) + outputContext?.scaleBy(x: 1.0, y: -1.0) + outputContext?.translateBy(x: 0, y: -size.height) // Draw base image. - CGContextDrawImage(outputContext, imageRect, self.CGImage) + outputContext?.draw(self.cgImage!, in: imageRect) // Draw effect image. if hasBlur { - CGContextSaveGState(outputContext) + outputContext?.saveGState() if let image = maskImage { - CGContextClipToMask(outputContext, imageRect, image.CGImage); + outputContext?.clip(to: imageRect, mask: image.cgImage!); } - CGContextDrawImage(outputContext, imageRect, effectImage.CGImage) - CGContextRestoreGState(outputContext) + outputContext?.draw(effectImage.cgImage!, in: imageRect) + outputContext?.restoreGState() } // Add in color tint. if let color = tintColor { - CGContextSaveGState(outputContext) - CGContextSetFillColorWithColor(outputContext, color.CGColor) - CGContextFillRect(outputContext, imageRect) - CGContextRestoreGState(outputContext) + outputContext?.saveGState() + outputContext?.setFillColor(color.cgColor) + outputContext?.fill(imageRect) + outputContext?.restoreGState() } // Output image is ready. @@ -378,7 +380,7 @@ public extension UIImage { } extension UIImage { - public func imageRotatedByDegrees(degrees: CGFloat, flip: Bool) -> UIImage { + public func imageRotatedByDegrees(_ degrees: CGFloat, flip: Bool) -> UIImage { // let radiansToDegrees: (CGFloat) -> CGFloat = { // return $0 * (180.0 / CGFloat(M_PI)) // } @@ -387,8 +389,8 @@ extension UIImage { } // calculate the size of the rotated view's containing box for our drawing space - let rotatedViewBox = UIView(frame: CGRect(origin: CGPointZero, size: size)) - let t = CGAffineTransformMakeRotation(degreesToRadians(degrees)); + let rotatedViewBox = UIView(frame: CGRect(origin: CGPoint.zero, size: size)) + let t = CGAffineTransform(rotationAngle: degreesToRadians(degrees)); rotatedViewBox.transform = t let rotatedSize = rotatedViewBox.frame.size @@ -397,10 +399,10 @@ extension UIImage { let bitmap = UIGraphicsGetCurrentContext() // Move the origin to the middle of the image so we will rotate and scale around the center. - CGContextTranslateCTM(bitmap, rotatedSize.width / 2.0, rotatedSize.height / 2.0); + bitmap?.translateBy(x: rotatedSize.width / 2.0, y: rotatedSize.height / 2.0); // // Rotate the image context - CGContextRotateCTM(bitmap, degreesToRadians(degrees)); + bitmap?.rotate(by: degreesToRadians(degrees)); // Now, draw the rotated/scaled image into the context var yFlip: CGFloat @@ -411,13 +413,13 @@ extension UIImage { yFlip = CGFloat(1.0) } - CGContextScaleCTM(bitmap, yFlip, -1.0) - CGContextDrawImage(bitmap, CGRectMake(-size.width / 2, -size.height / 2, size.width, size.height), CGImage) + bitmap?.scaleBy(x: yFlip, y: -1.0) + bitmap?.draw(cgImage!, in: CGRect(x: -size.width / 2, y: -size.height / 2, width: size.width, height: size.height)) let newImage = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() - return newImage + return newImage! } } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/SwiftExtensions/Logs.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/SwiftExtensions/Logs.swift index 4417bb87f4..480ce1e341 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/SwiftExtensions/Logs.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/SwiftExtensions/Logs.swift @@ -4,6 +4,6 @@ import UIKit -func log(text:String) { +func log(_ text:String) { NSLog(text) -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/SwiftExtensions/Numbers.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/SwiftExtensions/Numbers.swift index b1b91522fa..9f3523f269 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/SwiftExtensions/Numbers.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/SwiftExtensions/Numbers.swift @@ -5,19 +5,19 @@ import Foundation public extension Int { - public func format(f: String) -> String { - return NSString(format: "%\(f)d", self) as String + public func format(_ f: String) -> String { + return NSString(format: "%\(f)d" as NSString, self) as String } } public extension Double { - public func format(f: String) -> String { - return NSString(format: "%\(f)f", self) as String + public func format(_ f: String) -> String { + return NSString(format: "%\(f)f" as NSString, self) as String } } -public extension NSTimeInterval { +public extension TimeInterval { public var time:String { - return String(format:"%02d:%02d:%02d.%03d", Int((self/3600.0)%60),Int((self/60.0)%60), Int((self) % 60 ), Int(self*1000 % 1000 ) ) + return String(format:"%02d:%02d:%02d.%03d", Int((self/3600.0).truncatingRemainder(dividingBy: 60)),Int((self/60.0).truncatingRemainder(dividingBy: 60)), Int((self).truncatingRemainder(dividingBy: 60) ), Int((self*1000).truncatingRemainder(dividingBy: 1000) ) ) } } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/SwiftExtensions/Promises.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/SwiftExtensions/Promises.swift index efd8e16d36..f24a2d91b3 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/SwiftExtensions/Promises.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/SwiftExtensions/Promises.swift @@ -7,14 +7,14 @@ import MBProgressHUD extension ARPromise { - func startUserAction(ignore: [String] = []) -> ARPromise { + func startUserAction(_ ignore: [String] = []) -> ARPromise { - let window = UIApplication.sharedApplication().windows[1] + let window = UIApplication.shared.windows[1] let hud = MBProgressHUD(window: window) - hud.mode = MBProgressHUDMode.Indeterminate + hud.mode = MBProgressHUDMode.indeterminate hud.removeFromSuperViewOnHide = true window.addSubview(hud) - window.bringSubviewToFront(hud) + window.bringSubview(toFront: hud) hud.show(true) then { (t: AnyObject!) -> () in @@ -34,17 +34,17 @@ extension ARPromise { return self } - func then(closure: (T!) -> ()) -> ARPromise { + func then(_ closure: @escaping (T!) -> ()) -> ARPromise { then(PromiseConsumer(closure: closure)) return self } - func after(closure: () -> ()) -> ARPromise { + func after(_ closure: @escaping () -> ()) -> ARPromise { then(PromiseConsumerEmpty(closure: closure)) return self } - func failure(withClosure closure: (JavaLangException!) -> ()) -> ARPromise { + func failure(withClosure closure: @escaping (JavaLangException!) -> ()) -> ARPromise { failure(PromiseConsumer(closure: closure)) return self } @@ -54,11 +54,11 @@ class PromiseConsumer: NSObject, ARConsumer { let closure: (T!) -> () - init(closure: (T!) -> ()) { + init(closure: @escaping (T!) -> ()) { self.closure = closure } - func applyWithId(t: AnyObject!) { + func apply(withId t: Any!) { closure(t as? T) } } @@ -67,11 +67,11 @@ class PromiseConsumerEmpty: NSObject, ARConsumer { let closure: () -> () - init(closure: () -> ()) { + init(closure: @escaping () -> ()) { self.closure = closure } - func applyWithId(t: AnyObject!) { + func apply(withId t: Any!) { closure() } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/SwiftExtensions/Strings.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/SwiftExtensions/Strings.swift index 0e718ee9c1..65a8c2e474 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/SwiftExtensions/Strings.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/SwiftExtensions/Strings.swift @@ -11,47 +11,47 @@ public extension String { public var length: Int { return self.characters.count } - public func indexOf(str: String) -> Int? { - if let range = rangeOfString(str) { - return startIndex.distanceTo(range.startIndex) + public func indexOf(_ str: String) -> Int? { + if let range = range(of: str) { + return characters.distance(from: startIndex, to: range.lowerBound) } else { return nil } } public func trim() -> String { - return stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceCharacterSet()); + return trimmingCharacters(in: CharacterSet.whitespaces); } public subscript (i: Int) -> Character { - return self[self.startIndex.advancedBy(i)] + return self[self.characters.index(self.startIndex, offsetBy: i)] } public subscript (i: Int) -> String { return String(self[i] as Character) } - public func first(count: Int) -> String { + public func first(_ count: Int) -> String { let realCount = min(count, length); - return substringToIndex(startIndex.advancedBy(realCount)); + return substring(to: characters.index(startIndex, offsetBy: realCount)); } - public func skip(count: Int) -> String { + public func skip(_ count: Int) -> String { let realCount = min(count, length); - return substringFromIndex(startIndex.advancedBy(realCount)) + return substring(from: characters.index(startIndex, offsetBy: realCount)) } - public func strip(set: NSCharacterSet) -> String { - return componentsSeparatedByCharactersInSet(set).joinWithSeparator("") + public func strip(_ set: CharacterSet) -> String { + return components(separatedBy: set).joined(separator: "") } - public func replace(src: String, dest:String) -> String { - return stringByReplacingOccurrencesOfString(src, withString: dest, options: NSStringCompareOptions(), range: nil) + public func replace(_ src: String, dest:String) -> String { + return replacingOccurrences(of: src, with: dest, options: NSString.CompareOptions(), range: nil) } public func toLong() -> Int64? { - return NSNumberFormatter().numberFromString(self)?.longLongValue + return NumberFormatter().number(from: self)?.int64Value } public func toJLong() -> jlong { @@ -63,55 +63,55 @@ public extension String { if (trimmed.isEmpty){ return "#"; } - let letters = NSCharacterSet.letterCharacterSet() + let letters = CharacterSet.letters let res: String = self[0]; - if (res.rangeOfCharacterFromSet(letters) != nil) { - return res.uppercaseString; + if (res.rangeOfCharacter(from: letters) != nil) { + return res.uppercased(); } else { return "#"; } } - public func hasPrefixInWords(prefix: String) -> Bool { - var components = self.componentsSeparatedByString(" ") + public func hasPrefixInWords(_ prefix: String) -> Bool { + var components = self.components(separatedBy: " ") for i in 0.. Bool { - return self.rangeOfString(text, options: NSStringCompareOptions.CaseInsensitiveSearch, range: nil, locale: nil) != nil + public func contains(_ text: String) -> Bool { + return self.range(of: text, options: NSString.CompareOptions.caseInsensitive, range: nil, locale: nil) != nil } - public func startsWith(text: String) -> Bool { - let range = rangeOfString(text) + public func startsWith(_ text: String) -> Bool { + let range = self.range(of: text) if range != nil { - return range!.startIndex == startIndex + return range!.lowerBound == startIndex } return false } - public func rangesOfString(text: String) -> [Range] { + public func rangesOfString(_ text: String) -> [Range] { var res = [Range]() - var searchRange = Range(start: self.startIndex, end: self.endIndex) - while true { - let found = self.rangeOfString(text, options: NSStringCompareOptions.CaseInsensitiveSearch, range: searchRange, locale: nil) - if found != nil { - res.append(found!) - searchRange = Range(start: found!.endIndex, end: self.endIndex) - } else { - break - } - } - +// var searchRange = (self.characters.indices) +// while true { +// let found = self.range(of: text, options: NSString.CompareOptions.caseInsensitive, range: searchRange, locale: nil) +// if found != nil { +// res.append(found!) +// searchRange = (found!.upperBound ..< self.endIndex) +// } else { +// break +// } +// } +// return res } - public func repeatString(count: Int) -> String { + public func repeatString(_ count: Int) -> String { var res = "" for _ in 0.. Bool { - if let url = NSURL(string: self) { - return UIApplication.sharedApplication().canOpenURL(url) + if let url = URL(string: self) { + return UIApplication.shared.canOpenURL(url) } return false } @@ -141,14 +141,14 @@ public extension String { public extension NSAttributedString { - public func append(text: NSAttributedString) -> NSAttributedString { + public func append(_ text: NSAttributedString) -> NSAttributedString { let res = NSMutableAttributedString() - res.appendAttributedString(self) - res.appendAttributedString(text) + // res.append(self) + // res.append(text) return res } - public func append(text: String, font: UIFont) -> NSAttributedString { + public func append(_ text: String, font: UIFont) -> NSAttributedString { return append(NSAttributedString(string: text, attributes: [NSFontAttributeName: font])) } @@ -159,12 +159,12 @@ public extension NSAttributedString { public extension NSMutableAttributedString { - public func appendFont(font: UIFont) { + public func appendFont(_ font: UIFont) { self.addAttribute(NSFontAttributeName, value: font, range: NSMakeRange(0, self.length)) } - public func appendColor(color: UIColor) { - self.addAttribute(NSForegroundColorAttributeName, value: color.CGColor, range: NSMakeRange(0, self.length)) + public func appendColor(_ color: UIColor) { + self.addAttribute(NSForegroundColorAttributeName, value: color.cgColor, range: NSMakeRange(0, self.length)) } } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/SwiftExtensions/Views.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/SwiftExtensions/Views.swift index c4ec3502e4..04779a14ee 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/SwiftExtensions/Views.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/SwiftExtensions/Views.swift @@ -9,7 +9,7 @@ import AVFoundation private var targetReference = "target" public extension UITapGestureRecognizer { - public convenience init(closure: ()->()){ + public convenience init(closure: @escaping ()->()){ let target = ClosureTarget(closure: closure) self.init(target: target, action: #selector(ClosureTarget.invoke)) setAssociatedObject(self, value: target, associativeKey: &targetReference) @@ -21,7 +21,7 @@ public extension UIView { set (value) { if value != nil { self.addGestureRecognizer(UITapGestureRecognizer(closure: value!)) - self.userInteractionEnabled = true + self.isUserInteractionEnabled = true } } get { @@ -32,9 +32,9 @@ public extension UIView { private class ClosureTarget { - private let closure: ()->() + fileprivate let closure: ()->() - init(closure: ()->()) { + init(closure: @escaping ()->()) { self.closure = closure } @@ -48,43 +48,43 @@ private class ClosureTarget { public extension UIView { public func hideView() { - self.hidden = true + self.isHidden = true // UIView.animateWithDuration(0.2, animations: { () -> Void in // self.alpha = 0 // }) } public func showView() { - self.hidden = false + self.isHidden = false // UIView.animateWithDuration(0.2, animations: { () -> Void in // self.alpha = 1 // }) } public func showViewAnimated() { - UIView.animateWithDuration(0.2, animations: { () -> Void in + UIView.animate(withDuration: 0.2, animations: { () -> Void in self.alpha = 1 }) } public func hideViewAnimated() { - UIView.animateWithDuration(0.2, animations: { () -> Void in + UIView.animate(withDuration: 0.2, animations: { () -> Void in self.alpha = 0 }) } public func showViewPop() { - UIView.animateWithDuration(1, delay: 0, usingSpringWithDamping: 0.7, - initialSpringVelocity: 0.6, options: .CurveEaseOut, animations: { () -> Void in - self.transform = CGAffineTransformIdentity; + UIView.animate(withDuration: 1, delay: 0, usingSpringWithDamping: 0.7, + initialSpringVelocity: 0.6, options: .curveEaseOut, animations: { () -> Void in + self.transform = CGAffineTransform.identity; self.alpha = 1 }, completion: nil) } public func hideViewPop() { - UIView.animateWithDuration(1, delay: 0, usingSpringWithDamping: 0.7, - initialSpringVelocity: 1.0,options: .CurveEaseOut,animations: { () -> Void in - self.transform = CGAffineTransformMakeScale(0.01, 0.01) + UIView.animate(withDuration: 1, delay: 0, usingSpringWithDamping: 0.7, + initialSpringVelocity: 1.0,options: .curveEaseOut,animations: { () -> Void in + self.transform = CGAffineTransform(scaleX: 0.01, y: 0.01) self.alpha = 0 }, completion: nil) } @@ -97,53 +97,53 @@ public extension UIView { // public var top: CGFloat { get { return frame.minY } } // public var bottom: CGFloat { get { return frame.maxY } } - public func centerIn(rect: CGRect) { - self.frame = CGRectMake(rect.origin.x + (rect.width - self.bounds.width) / 2, rect.origin.y + (rect.height - self.bounds.height) / 2, - self.bounds.width, self.bounds.height) + public func centerIn(_ rect: CGRect) { + self.frame = CGRect(x: rect.origin.x + (rect.width - self.bounds.width) / 2, y: rect.origin.y + (rect.height - self.bounds.height) / 2, + width: self.bounds.width, height: self.bounds.height) } - public func under(rect: CGRect, offset: CGFloat) { - self.frame = CGRectMake(rect.origin.x + (rect.width - self.bounds.width) / 2, rect.origin.y + rect.height + offset, - self.bounds.width, self.bounds.height) + public func under(_ rect: CGRect, offset: CGFloat) { + self.frame = CGRect(x: rect.origin.x + (rect.width - self.bounds.width) / 2, y: rect.origin.y + rect.height + offset, + width: self.bounds.width, height: self.bounds.height) } - public func topIn(rect: CGRect) { - self.frame = CGRectMake(rect.origin.x + (rect.width - self.bounds.width) / 2, rect.origin.y, - self.bounds.width, self.bounds.height) + public func topIn(_ rect: CGRect) { + self.frame = CGRect(x: rect.origin.x + (rect.width - self.bounds.width) / 2, y: rect.origin.y, + width: self.bounds.width, height: self.bounds.height) } } // Text measuring -public class UIViewMeasure { +open class UIViewMeasure { - public class func measureText(text: String, width: CGFloat, fontSize: CGFloat) -> CGSize { - return UIViewMeasure.measureText(text, width: width, font: UIFont.systemFontOfSize(fontSize)) + open class func measureText(_ text: String, width: CGFloat, fontSize: CGFloat) -> CGSize { + return UIViewMeasure.measureText(text, width: width, font: UIFont.systemFont(ofSize: fontSize)) } - public class func measureText(text: String, width: CGFloat, font: UIFont) -> CGSize { + open class func measureText(_ text: String, width: CGFloat, font: UIFont) -> CGSize { // Building paragraph styles let style = NSMutableParagraphStyle() - style.lineBreakMode = NSLineBreakMode.ByWordWrapping + style.lineBreakMode = NSLineBreakMode.byWordWrapping // Measuring text with reduced width - let rect = text.boundingRectWithSize(CGSize(width: width - 2, height: CGFloat.max), - options: NSStringDrawingOptions.UsesLineFragmentOrigin, + let rect = text.boundingRect(with: CGSize(width: width - 2, height: CGFloat.greatestFiniteMagnitude), + options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes: [NSFontAttributeName: font, NSParagraphStyleAttributeName: style], context: nil) // Returning size with expanded width - return CGSizeMake(ceil(rect.width + 2), CGFloat(ceil(rect.height))) + return CGSize(width: ceil(rect.width + 2), height: CGFloat(ceil(rect.height))) } - public class func measureText(attributedText: NSAttributedString, width: CGFloat) -> CGSize { + open class func measureText(_ attributedText: NSAttributedString, width: CGFloat) -> CGSize { // Measuring text with reduced width - let rect = attributedText.boundingRectWithSize(CGSize(width: width - 2, height: CGFloat.max), options: [.UsesLineFragmentOrigin, .UsesFontLeading], context: nil) + let rect = attributedText.boundingRect(with: CGSize(width: width - 2, height: CGFloat.greatestFiniteMagnitude), options: [.usesLineFragmentOrigin, .usesFontLeading], context: nil) // Returning size with expanded width and height - return CGSizeMake(ceil(rect.width + 2), CGFloat(ceil(rect.height))) + return CGSize(width: ceil(rect.width + 2), height: CGFloat(ceil(rect.height))) } } @@ -152,7 +152,7 @@ public class UIViewMeasure { private var registeredCells = "cells!" public extension UITableView { - private func cellTypeForClass(cellClass: AnyClass) -> String { + fileprivate func cellTypeForClass(_ cellClass: AnyClass) -> String { let cellReuseId = "\(cellClass)" var registered: ([String])! = getAssociatedObject(self, associativeKey: ®isteredCells) var found = false @@ -168,30 +168,30 @@ public extension UITableView { } if !found { - registerClass(cellClass, forCellReuseIdentifier: cellReuseId) + register(cellClass, forCellReuseIdentifier: cellReuseId) } return cellReuseId } - public func dequeueCell(cellClass: AnyClass, indexPath: NSIndexPath) -> UITableViewCell { + public func dequeueCell(_ cellClass: AnyClass, indexPath: IndexPath) -> UITableViewCell { let reuseId = cellTypeForClass(cellClass) - return self.dequeueReusableCellWithIdentifier(reuseId, forIndexPath: indexPath) + return self.dequeueReusableCell(withIdentifier: reuseId, for: indexPath) } - public func dequeueCell(indexPath: NSIndexPath) -> T { + public func dequeueCell(_ indexPath: IndexPath) -> T where T: UITableViewCell { let reuseId = cellTypeForClass(T.self) - return self.dequeueReusableCellWithIdentifier(reuseId, forIndexPath: indexPath) as! T + return self.dequeueReusableCell(withIdentifier: reuseId, for: indexPath) as! T } - public func visibleCellForIndexPath(path: NSIndexPath) -> UITableViewCell? { + public func visibleCellForIndexPath(_ path: IndexPath) -> UITableViewCell? { if indexPathsForVisibleRows == nil { return nil } for i in 0.. String { + func encodeText(_ key: Int32) -> String { var res = "" for i in 0.. UIImageView? { - if view.isKindOfClass(UIImageView) && view.bounds.height <= 1.0 { + fileprivate func hairlineImageViewInNavigationBar(_ view: UIView) -> UIImageView? { + if view.isKind(of: UIImageView.self) && view.bounds.height <= 1.0 { return (view as! UIImageView) } @@ -369,52 +369,52 @@ extension UINavigationBar { extension AVAsset { func videoOrientation() -> (orientation: UIInterfaceOrientation, device: AVCaptureDevicePosition) { - var orientation: UIInterfaceOrientation = .Unknown - var device: AVCaptureDevicePosition = .Unspecified + var orientation: UIInterfaceOrientation = .unknown + var device: AVCaptureDevicePosition = .unspecified - let tracks :[AVAssetTrack] = self.tracksWithMediaType(AVMediaTypeVideo) + let tracks :[AVAssetTrack] = self.tracks(withMediaType: AVMediaTypeVideo) if let videoTrack = tracks.first { let t = videoTrack.preferredTransform if (t.a == 0 && t.b == 1.0 && t.d == 0) { - orientation = .Portrait + orientation = .portrait if t.c == 1.0 { - device = .Front + device = .front } else if t.c == -1.0 { - device = .Back + device = .back } } else if (t.a == 0 && t.b == -1.0 && t.d == 0) { - orientation = .PortraitUpsideDown + orientation = .portraitUpsideDown if t.c == -1.0 { - device = .Front + device = .front } else if t.c == 1.0 { - device = .Back + device = .back } } else if (t.a == 1.0 && t.b == 0 && t.c == 0) { - orientation = .LandscapeRight + orientation = .landscapeRight if t.d == -1.0 { - device = .Front + device = .front } else if t.d == 1.0 { - device = .Back + device = .back } } else if (t.a == -1.0 && t.b == 0 && t.c == 0) { - orientation = .LandscapeLeft + orientation = .landscapeLeft if t.d == 1.0 { - device = .Front + device = .front } else if t.d == -1.0 { - device = .Back + device = .back } } } return (orientation, device) } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Utils/AAAudioManager.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Utils/AAAudioManager.swift index 5af6d6543b..6e813cb635 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Utils/AAAudioManager.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Utils/AAAudioManager.swift @@ -5,39 +5,39 @@ import Foundation import AVFoundation -public class AAAudioManager: NSObject, AVAudioPlayerDelegate { +open class AAAudioManager: NSObject, AVAudioPlayerDelegate { - private static let sharedManager = AAAudioManager() + fileprivate static let sharedManager = AAAudioManager() - public static func sharedAudio() -> AAAudioManager { + open static func sharedAudio() -> AAAudioManager { return sharedManager } - private var isRinging = false - private var ringtonePlaying = false - private var ringtonePlayer: AVAudioPlayer! = nil - private var audioRouter = AAAudioRouter() + fileprivate var isRinging = false + fileprivate var ringtonePlaying = false + fileprivate var ringtonePlayer: AVAudioPlayer! = nil + fileprivate var audioRouter = AAAudioRouter() - private var ringtoneSound:SystemSoundID = 0 - private var isVisible = false + fileprivate var ringtoneSound:SystemSoundID = 0 + fileprivate var isVisible = false - private var isEnabled: Bool = false - private var isVideoPreferred = false - private var openedConnections: Int = 0 + fileprivate var isEnabled: Bool = false + fileprivate var isVideoPreferred = false + fileprivate var openedConnections: Int = 0 public override init() { super.init() } - public func appVisible() { + open func appVisible() { isVisible = true } - public func appHidden() { + open func appHidden() { isVisible = false } - public func callStart(call: ACCallVM) { + open func callStart(_ call: ACCallVM) { isVideoPreferred = call.isVideoPreferred if !call.isOutgoing { @@ -47,7 +47,7 @@ public class AAAudioManager: NSObject, AVAudioPlayerDelegate { audioRouter.batchedUpdate { audioRouter.category = AVAudioSessionCategoryPlayAndRecord audioRouter.mode = AVAudioSessionModeDefault - audioRouter.currentRoute = .Speaker + audioRouter.currentRoute = .speaker audioRouter.isEnabled = true } ringtoneStart() @@ -61,53 +61,53 @@ public class AAAudioManager: NSObject, AVAudioPlayerDelegate { audioRouter.category = AVAudioSessionCategoryPlayAndRecord audioRouter.mode = AVAudioSessionModeVoiceChat if isVideoPreferred { - audioRouter.currentRoute = .Speaker + audioRouter.currentRoute = .speaker } else { - audioRouter.currentRoute = .Receiver + audioRouter.currentRoute = .receiver } audioRouter.isEnabled = true } } } - public func callAnswered(call: ACCallVM) { + open func callAnswered(_ call: ACCallVM) { ringtoneEnd() isRinging = false audioRouter.batchedUpdate { audioRouter.mode = AVAudioSessionModeVoiceChat if isVideoPreferred { - audioRouter.currentRoute = .Speaker + audioRouter.currentRoute = .speaker } else { - audioRouter.currentRoute = .Receiver + audioRouter.currentRoute = .receiver } } } - public func callEnd(call: ACCallVM) { + open func callEnd(_ call: ACCallVM) { ringtoneEnd() isRinging = false isEnabled = false audioRouter.batchedUpdate { audioRouter.category = AVAudioSessionCategorySoloAmbient audioRouter.mode = AVAudioSessionModeDefault - audioRouter.currentRoute = .Receiver + audioRouter.currentRoute = .receiver audioRouter.isEnabled = false } } - public func peerConnectionStarted() { + open func peerConnectionStarted() { openedConnections += 1 print("📡 AudioManager: peerConnectionStarted \(self.openedConnections)") audioRouter.isRTCEnabled = openedConnections > 0 } - public func peerConnectionEnded() { + open func peerConnectionEnded() { openedConnections -= 1 print("📡 AudioManager: peerConnectionEnded \(self.openedConnections)") audioRouter.isRTCEnabled = openedConnections > 0 } - private func ringtoneStart() { + fileprivate func ringtoneStart() { if ringtonePlaying { return } @@ -115,7 +115,7 @@ public class AAAudioManager: NSObject, AVAudioPlayerDelegate { ringtonePlaying = true do { - self.ringtonePlayer = try AVAudioPlayer(contentsOfURL: NSURL(fileURLWithPath: NSBundle.framework.pathForResource("ringtone", ofType: "m4a")!)) + self.ringtonePlayer = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: Bundle.framework.path(forResource: "ringtone", ofType: "m4a")!)) self.ringtonePlayer.delegate = self self.ringtonePlayer.numberOfLoops = -1 self.ringtonePlayer.volume = 1.0 @@ -126,11 +126,11 @@ public class AAAudioManager: NSObject, AVAudioPlayerDelegate { } } - private func vibrate() { + fileprivate func vibrate() { if #available(iOS 9.0, *) { AudioServicesPlayAlertSoundWithCompletion(1352) { - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(NSEC_PER_SEC)), dispatch_get_main_queue()) { () -> Void in + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Double(Int64(NSEC_PER_SEC)) / Double(NSEC_PER_SEC)) { () -> Void in if self.isRinging { self.vibrate() } @@ -138,7 +138,7 @@ public class AAAudioManager: NSObject, AVAudioPlayerDelegate { } } else { AudioServicesPlayAlertSound(1352) - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(NSEC_PER_SEC)), dispatch_get_main_queue()) { () -> Void in + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Double(Int64(NSEC_PER_SEC)) / Double(NSEC_PER_SEC)) { () -> Void in if self.isRinging { self.vibrate() } @@ -146,35 +146,35 @@ public class AAAudioManager: NSObject, AVAudioPlayerDelegate { } } - private func notificationRingtone(call: ACCallVM) { + fileprivate func notificationRingtone(_ call: ACCallVM) { dispatchOnUi() { let notification = UILocalNotification() if call.peer.isGroup { let groupName = Actor.getGroupWithGid(call.peer.peerId).getNameModel().get() - notification.alertBody = AALocalized("CallGroupText").replace("{name}", dest: groupName) + notification.alertBody = AALocalized("CallGroupText").replace("{name}", dest: groupName!) if #available(iOS 8.2, *) { notification.alertTitle = AALocalized("CallGroupTitle") } } else if call.peer.isPrivate { let userName = Actor.getUserWithUid(call.peer.peerId).getNameModel().get() - notification.alertBody = AALocalized("CallPrivateText").replace("{name}", dest: userName) + notification.alertBody = AALocalized("CallPrivateText").replace("{name}", dest: userName!) if #available(iOS 8.2, *) { notification.alertTitle = AALocalized("CallPrivateTitle") } } notification.soundName = "ringtone.m4a" - UIApplication.sharedApplication().presentLocalNotificationNow(notification) + UIApplication.shared.presentLocalNotificationNow(notification) } - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(10 * NSEC_PER_SEC)), dispatch_get_main_queue()) { () -> Void in + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Double(Int64(10 * NSEC_PER_SEC)) / Double(NSEC_PER_SEC)) { () -> Void in if self.isRinging { self.notificationRingtone(call) } } } - private func ringtoneEnd() { + fileprivate func ringtoneEnd() { if !ringtonePlaying { return } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Utils/AAAudioRouter.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Utils/AAAudioRouter.swift index aa8a37bff7..855329dd7f 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Utils/AAAudioRouter.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Utils/AAAudioRouter.swift @@ -6,16 +6,16 @@ import Foundation import AVFoundation public enum Route { - case Speaker - case Receiver + case speaker + case receiver } -public class AAAudioRouter { +open class AAAudioRouter { - private var isBatchedUpdate = false - private var isInvalidated = false + fileprivate var isBatchedUpdate = false + fileprivate var isInvalidated = false - public var isEnabled = false { + open var isEnabled = false { willSet(v) { if isEnabled != v { isInvalidated = true @@ -26,7 +26,7 @@ public class AAAudioRouter { } } - public var isRTCEnabled = false { + open var isRTCEnabled = false { willSet(v) { if isRTCEnabled != v { isInvalidated = true @@ -37,7 +37,7 @@ public class AAAudioRouter { } } - public var currentRoute = Route.Receiver { + open var currentRoute = Route.receiver { willSet(v) { if currentRoute != v { isInvalidated = true @@ -48,7 +48,7 @@ public class AAAudioRouter { } } - public var mode = AVAudioSessionModeDefault { + open var mode = AVAudioSessionModeDefault { willSet(v) { if mode != v { isInvalidated = true @@ -59,7 +59,7 @@ public class AAAudioRouter { } } - public var category = AVAudioSessionCategorySoloAmbient { + open var category = AVAudioSessionCategorySoloAmbient { willSet(v) { if category != v { isInvalidated = true @@ -72,19 +72,19 @@ public class AAAudioRouter { public init() { fixSession() - NSNotificationCenter.defaultCenter().addObserverForName(AVAudioSessionRouteChangeNotification, - object: nil, queue: NSOperationQueue.mainQueue()) { (note) -> Void in - let notification: NSNotification = note as NSNotification - if let info = notification.userInfo { + NotificationCenter.default.addObserver(forName: NSNotification.Name.AVAudioSessionRouteChange, + object: nil, queue: OperationQueue.main) { (note) -> Void in + let notification: Notification = note as Notification + if let info = (notification as NSNotification).userInfo { let numberReason: NSNumber = info[AVAudioSessionRouteChangeReasonKey] as! NSNumber - if let reason = AVAudioSessionRouteChangeReason(rawValue: UInt(numberReason.integerValue)) { + if let reason = AVAudioSessionRouteChangeReason(rawValue: UInt(numberReason.intValue)) { self.routeChanged(reason) } } } } - func batchedUpdate(@noescape closure: ()->()) { + func batchedUpdate(_ closure: ()->()) { isInvalidated = false isBatchedUpdate = true closure() @@ -95,14 +95,14 @@ public class AAAudioRouter { } } - private func onChanged() { + fileprivate func onChanged() { if !isBatchedUpdate && isInvalidated { isInvalidated = false fixSession() } } - private func fixSession() { + fileprivate func fixSession() { let session = AVAudioSession.sharedInstance() @@ -123,10 +123,10 @@ public class AAAudioRouter { if let route: AVAudioSessionRouteDescription = session.currentRoute { for port in route.outputs { let portDescription: AVAudioSessionPortDescription = port as AVAudioSessionPortDescription - if (self.currentRoute == .Receiver && portDescription.portType != AVAudioSessionPortBuiltInReceiver) { - try session.overrideOutputAudioPort(.None) - } else if (self.currentRoute == .Speaker && portDescription.portType != AVAudioSessionPortBuiltInSpeaker) { - try session.overrideOutputAudioPort(AVAudioSessionPortOverride.Speaker) + if (self.currentRoute == .receiver && portDescription.portType != AVAudioSessionPortBuiltInReceiver) { + try session.overrideOutputAudioPort(.none) + } else if (self.currentRoute == .speaker && portDescription.portType != AVAudioSessionPortBuiltInSpeaker) { + try session.overrideOutputAudioPort(AVAudioSessionPortOverride.speaker) } } } @@ -146,10 +146,10 @@ public class AAAudioRouter { if let route: AVAudioSessionRouteDescription = session.currentRoute { for port in route.outputs { let portDescription: AVAudioSessionPortDescription = port as AVAudioSessionPortDescription - if (self.currentRoute == .Receiver && portDescription.portType != AVAudioSessionPortBuiltInReceiver) { - try session.overrideOutputAudioPort(.None) - } else if (self.currentRoute == .Speaker && portDescription.portType != AVAudioSessionPortBuiltInSpeaker) { - try session.overrideOutputAudioPort(AVAudioSessionPortOverride.Speaker) + if (self.currentRoute == .receiver && portDescription.portType != AVAudioSessionPortBuiltInReceiver) { + try session.overrideOutputAudioPort(.none) + } else if (self.currentRoute == .speaker && portDescription.portType != AVAudioSessionPortBuiltInSpeaker) { + try session.overrideOutputAudioPort(AVAudioSessionPortOverride.speaker) } } } @@ -165,7 +165,7 @@ public class AAAudioRouter { } } - private func isHeadsetPluggedIn() -> Bool { + fileprivate func isHeadsetPluggedIn() -> Bool { let route: AVAudioSessionRouteDescription = AVAudioSession.sharedInstance().currentRoute for port in route.outputs { let portDescription: AVAudioSessionPortDescription = port as AVAudioSessionPortDescription @@ -176,21 +176,21 @@ public class AAAudioRouter { return false } - private func routeChanged(reason: AVAudioSessionRouteChangeReason) { - if reason == .NewDeviceAvailable { + fileprivate func routeChanged(_ reason: AVAudioSessionRouteChangeReason) { + if reason == .newDeviceAvailable { if isHeadsetPluggedIn() { - self.currentRoute = .Receiver + self.currentRoute = .receiver return } - } else if reason == .OldDeviceUnavailable { + } else if reason == .oldDeviceUnavailable { if !isHeadsetPluggedIn() { - self.currentRoute = .Receiver + self.currentRoute = .receiver return } } - if reason == .Override || reason == .RouteConfigurationChange { + if reason == .override || reason == .routeConfigurationChange { fixSession() } } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Utils/AAFileTypes.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Utils/AAFileTypes.swift index 8f8fda3b80..0586af76e7 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Utils/AAFileTypes.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Utils/AAFileTypes.swift @@ -10,88 +10,88 @@ let UTTAll = [ ] let AAFileTypes = [ - "mp3" :AAFileType.Music, - "m4a" :AAFileType.Music, - "ogg" :AAFileType.Music, - "flac" :AAFileType.Music, - "alac" :AAFileType.Music, - "wav" :AAFileType.Music, - "wma" :AAFileType.Music, - "aac" :AAFileType.Music, + "mp3" :AAFileType.music, + "m4a" :AAFileType.music, + "ogg" :AAFileType.music, + "flac" :AAFileType.music, + "alac" :AAFileType.music, + "wav" :AAFileType.music, + "wma" :AAFileType.music, + "aac" :AAFileType.music, - "doc" :AAFileType.Doc, - "docm" :AAFileType.Doc, - "dot" :AAFileType.Doc, - "dotx" :AAFileType.Doc, - "epub" :AAFileType.Doc, - "fb2" :AAFileType.Doc, - "xml" :AAFileType.Doc, - "info" :AAFileType.Doc, - "tex" :AAFileType.Doc, - "stw" :AAFileType.Doc, - "sxw" :AAFileType.Doc, - "txt" :AAFileType.Doc, - "xlc" :AAFileType.Doc, - "odf" :AAFileType.Doc, - "odt" :AAFileType.Doc, - "ott" :AAFileType.Doc, - "rtf" :AAFileType.Doc, - "pages":AAFileType.Doc, - "ini" :AAFileType.Doc, + "doc" :AAFileType.doc, + "docm" :AAFileType.doc, + "dot" :AAFileType.doc, + "dotx" :AAFileType.doc, + "epub" :AAFileType.doc, + "fb2" :AAFileType.doc, + "xml" :AAFileType.doc, + "info" :AAFileType.doc, + "tex" :AAFileType.doc, + "stw" :AAFileType.doc, + "sxw" :AAFileType.doc, + "txt" :AAFileType.doc, + "xlc" :AAFileType.doc, + "odf" :AAFileType.doc, + "odt" :AAFileType.doc, + "ott" :AAFileType.doc, + "rtf" :AAFileType.doc, + "pages":AAFileType.doc, + "ini" :AAFileType.doc, - "xls" :AAFileType.Spreadsheet, - "xlsx" :AAFileType.Spreadsheet, - "xlsm" :AAFileType.Spreadsheet, - "xlsb" :AAFileType.Spreadsheet, - "numbers":AAFileType.Spreadsheet, + "xls" :AAFileType.spreadsheet, + "xlsx" :AAFileType.spreadsheet, + "xlsm" :AAFileType.spreadsheet, + "xlsb" :AAFileType.spreadsheet, + "numbers":AAFileType.spreadsheet, - "jpg" :AAFileType.Picture, - "jpeg" :AAFileType.Picture, - "jp2" :AAFileType.Picture, - "jps" :AAFileType.Picture, - "gif" :AAFileType.Picture, - "tiff" :AAFileType.Picture, - "png" :AAFileType.Picture, - "psd" :AAFileType.Picture, - "webp" :AAFileType.Picture, - "ico" :AAFileType.Picture, - "pcx" :AAFileType.Picture, - "tga" :AAFileType.Picture, - "raw" :AAFileType.Picture, - "svg" :AAFileType.Picture, + "jpg" :AAFileType.picture, + "jpeg" :AAFileType.picture, + "jp2" :AAFileType.picture, + "jps" :AAFileType.picture, + "gif" :AAFileType.picture, + "tiff" :AAFileType.picture, + "png" :AAFileType.picture, + "psd" :AAFileType.picture, + "webp" :AAFileType.picture, + "ico" :AAFileType.picture, + "pcx" :AAFileType.picture, + "tga" :AAFileType.picture, + "raw" :AAFileType.picture, + "svg" :AAFileType.picture, - "mp4" :AAFileType.Video, - "3gp" :AAFileType.Video, - "m4v" :AAFileType.Video, - "webm" :AAFileType.Video, + "mp4" :AAFileType.video, + "3gp" :AAFileType.video, + "m4v" :AAFileType.video, + "webm" :AAFileType.video, - "ppt" :AAFileType.Presentation, - "key" :AAFileType.Presentation, - "keynote" :AAFileType.Presentation, + "ppt" :AAFileType.presentation, + "key" :AAFileType.presentation, + "keynote" :AAFileType.presentation, - "pdf" :AAFileType.PDF, - "apk" :AAFileType.APK, - "rar" :AAFileType.RAR, - "zip" :AAFileType.ZIP, - "csv" :AAFileType.CSV, + "pdf" :AAFileType.pdf, + "apk" :AAFileType.apk, + "rar" :AAFileType.rar, + "zip" :AAFileType.zip, + "csv" :AAFileType.csv, - "xhtm" :AAFileType.HTML, - "htm" :AAFileType.HTML, - "html" :AAFileType.HTML, + "xhtm" :AAFileType.html, + "htm" :AAFileType.html, + "html" :AAFileType.html, ] enum AAFileType { - case Music - case Doc - case Spreadsheet - case Picture - case Video - case Presentation - case PDF - case APK - case RAR - case ZIP - case CSV - case HTML - case UNKNOWN -} \ No newline at end of file + case music + case doc + case spreadsheet + case picture + case video + case presentation + case pdf + case apk + case rar + case zip + case csv + case html + case unknown +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Utils/AAHashMap.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Utils/AAHashMap.swift index 26f7f4b414..741e3be126 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Utils/AAHashMap.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Utils/AAHashMap.swift @@ -12,7 +12,7 @@ struct AAHashMap { } } - mutating func setKey(key: Int64, withValue val: T?) { + mutating func setKey(_ key: Int64, withValue val: T?) { let hashedString = Int(abs(key) % 10) if let collisionList = table[hashedString] { collisionList.upsertNodeWithKey(key, AndValue: val) @@ -21,7 +21,7 @@ struct AAHashMap { table[hashedString]!.upsertNodeWithKey(key, AndValue: val) } } - func getValueAtKey(key: Int64) -> T? { + func getValueAtKey(_ key: Int64) -> T? { let hashedString = Int(abs(key) % 10) if let collisionList = table[hashedString] { return collisionList.findNodeWithKey(key)?.value @@ -34,7 +34,7 @@ struct AAHashMap { struct SinglyLinkedList { var head = CCHeadNode>() - func findNodeWithKey(key: Int64) -> CCSinglyNode? { + func findNodeWithKey(_ key: Int64) -> CCSinglyNode? { var res: CCSinglyNode? autoreleasepool { if var currentNode = head.next { @@ -55,7 +55,7 @@ struct SinglyLinkedList { return res } - func upsertNodeWithKey(key: Int64, AndValue val: T?) -> CCSinglyNode { + func upsertNodeWithKey(_ key: Int64, AndValue val: T?) -> CCSinglyNode { if var currentNode = head.next { while let nextNode = currentNode.next { if currentNode.key == key { diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Utils/AASwiftlyLRU.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Utils/AASwiftlyLRU.swift index 3a235e6774..97d77fdd38 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Utils/AASwiftlyLRU.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Utils/AASwiftlyLRU.swift @@ -45,7 +45,7 @@ class LinkedList { } - func addToHead(node: Node) { + func addToHead(_ node: Node) { if self.head == nil { self.head = node self.tail = node @@ -58,7 +58,7 @@ class LinkedList { } } - func remove(node: Node) { + func remove(_ node: Node) { if node === self.head { if self.head?.next != nil { self.head = self.head?.next @@ -94,8 +94,8 @@ class AASwiftlyLRU : CustomStringConvertible { let capacity: Int var length = 0 - private let queue: LinkedList - private var hashtable: [K : Node] + fileprivate let queue: LinkedList + fileprivate var hashtable: [K : Node] /** Least Recently Used "LRU" Cache, capacity is the number of elements to keep in the Cache. @@ -134,7 +134,7 @@ class AASwiftlyLRU : CustomStringConvertible { self.length += 1 } else { - hashtable.removeValueForKey(self.queue.tail!.key) + hashtable.removeValue(forKey: self.queue.tail!.key) self.queue.tail = self.queue.tail?.previous if let node = self.queue.tail { @@ -151,4 +151,4 @@ class AASwiftlyLRU : CustomStringConvertible { var description : String { return "SwiftlyLRU Cache(\(self.length)) \n" + self.queue.display() } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Utils/AATools.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Utils/AATools.swift index 083e99456d..3619e33601 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Utils/AATools.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Utils/AATools.swift @@ -7,39 +7,39 @@ import zipzap class AATools { - class func copyFileCommand(from: String, to: String) -> ACCommand { + class func copyFileCommand(_ from: String, to: String) -> ACCommand { return CopyCommand(from: from, to: to) } - class func zipDirectoryCommand(from: String, to: String) -> ACCommand { + class func zipDirectoryCommand(_ from: String, to: String) -> ACCommand { return ZipCommand(dir: from, to: to) } - class func isValidEmail(testStr:String) -> Bool { + class func isValidEmail(_ testStr:String) -> Bool { let emailRegEx = "^[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,6}" let emailTest = NSPredicate(format:"SELF MATCHES %@", emailRegEx) - return emailTest.evaluateWithObject(testStr) + return emailTest.evaluate(with: testStr) } } private class ZipCommand: BackgroundCommand { - private let dir: String - private let to: String + fileprivate let dir: String + fileprivate let to: String init(dir: String, to: String) { self.dir = dir self.to = to } - private override func backgroundTask() throws { - let rootPath = NSURL(fileURLWithPath: dir).lastPathComponent! + fileprivate override func backgroundTask() throws { + let rootPath = URL(fileURLWithPath: dir).lastPathComponent - let zip = try ZZArchive(URL: NSURL(fileURLWithPath: to), options: [ZZOpenOptionsCreateIfMissingKey: true]) + let zip = try ZZArchive(url: URL(fileURLWithPath: to), options: [ZZOpenOptionsCreateIfMissingKey: true]) - let subs = try NSFileManager.defaultManager().subpathsOfDirectoryAtPath(dir) + let subs = try FileManager.default.subpathsOfDirectory(atPath: dir) var entries = [ZZArchiveEntry]() for p in subs { @@ -49,15 +49,15 @@ private class ZipCommand: BackgroundCommand { // Check path type: directory or file? var isDir : ObjCBool = false - if NSFileManager.defaultManager().fileExistsAtPath(fullPath, isDirectory: &isDir) { + if FileManager.default.fileExists(atPath: fullPath, isDirectory: &isDir) { - if !isDir { + if !isDir.boolValue { // If file write file - entries.append(ZZArchiveEntry(fileName: destPath, compress: false, dataBlock: { (error) -> NSData! in + entries.append(ZZArchiveEntry(fileName: destPath, compress: false, dataBlock: { (error) -> Data! in // TODO: Error handling? - return NSData(contentsOfFile: fullPath)! + return (try! Data(contentsOf: URL(fileURLWithPath: fullPath))) })) } else { @@ -86,15 +86,15 @@ private class CopyCommand: BackgroundCommand { self.to = to } - private override func backgroundTask() throws { - try NSFileManager.defaultManager().copyItemAtPath(from, toPath: to) + fileprivate override func backgroundTask() throws { + try FileManager.default.copyItem(atPath: from, toPath: to) } } class BackgroundCommand: NSObject, ACCommand { - func startWithCallback(callback: ACCommandCallback!) { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) { () -> Void in + func start(with callback: ACCommandCallback!) { + DispatchQueue.global(priority: DispatchQueue.GlobalQueuePriority.default).async { () -> Void in do { try self.backgroundTask() callback.onResult(nil) @@ -107,4 +107,4 @@ class BackgroundCommand: NSObject, ACCommand { func backgroundTask() throws { } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Utils/Bundle.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Utils/Bundle.swift index fa12f51dab..a1513ef7d0 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Utils/Bundle.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Utils/Bundle.swift @@ -4,10 +4,10 @@ import Foundation -let frameworkBundle = NSBundle(identifier: "im.actor.ActorSDK")! +let frameworkBundle = Bundle(identifier: "im.actor.ActorSDK")! -public extension NSBundle { - static var framework: NSBundle { +public extension Bundle { + static var framework: Bundle { get { return frameworkBundle } @@ -15,11 +15,11 @@ public extension NSBundle { } public extension UIImage { - class func bundled(named: String) -> UIImage? { + class func bundled(_ named: String) -> UIImage? { if let appImage = UIImage(named: named) { return appImage } - return UIImage(named: named, inBundle: NSBundle.framework, compatibleWithTraitCollection: UITraitCollection(displayScale: UIScreen.mainScreen().scale)) + return UIImage(named: named, in: Bundle.framework, compatibleWith: UITraitCollection(displayScale: UIScreen.main.scale)) } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Utils/Categories/AACustomPresentationAnimationController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Utils/Categories/AACustomPresentationAnimationController.swift index 0040936b81..bcc8135847 100755 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Utils/Categories/AACustomPresentationAnimationController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Utils/Categories/AACustomPresentationAnimationController.swift @@ -8,7 +8,7 @@ import UIKit class AACustomPresentationAnimationController: NSObject, UIViewControllerAnimatedTransitioning { let isPresenting :Bool - let duration :NSTimeInterval = 0.5 + let duration :TimeInterval = 0.5 init(isPresenting: Bool) { self.isPresenting = isPresenting @@ -19,11 +19,11 @@ class AACustomPresentationAnimationController: NSObject, UIViewControllerAnimate // ---- UIViewControllerAnimatedTransitioning methods - func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval { + func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { return self.duration } - func animateTransition(transitionContext: UIViewControllerContextTransitioning) { + func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { if isPresenting { animatePresentationWithTransitionContext(transitionContext) } @@ -35,31 +35,31 @@ class AACustomPresentationAnimationController: NSObject, UIViewControllerAnimate // ---- Helper methods - func animatePresentationWithTransitionContext(transitionContext: UIViewControllerContextTransitioning) { - let presentedController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)! - let presentedControllerView = transitionContext.viewForKey(UITransitionContextToViewKey)! - let containerView = transitionContext.containerView() + func animatePresentationWithTransitionContext(_ transitionContext: UIViewControllerContextTransitioning) { + let presentedController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)! + let presentedControllerView = transitionContext.view(forKey: UITransitionContextViewKey.to)! + let containerView = transitionContext.containerView // Position the presented view off the top of the container view - presentedControllerView.frame = transitionContext.finalFrameForViewController(presentedController) + presentedControllerView.frame = transitionContext.finalFrame(for: presentedController) //presentedControllerView.center.y -= containerView!.bounds.size.height - containerView!.addSubview(presentedControllerView) + containerView.addSubview(presentedControllerView) // Animate the presented view to it's final position - UIView.animateWithDuration(self.duration, delay: 0.0, usingSpringWithDamping: 1.0, initialSpringVelocity: 0.0, options: .AllowUserInteraction, animations: { + UIView.animate(withDuration: self.duration, delay: 0.0, usingSpringWithDamping: 1.0, initialSpringVelocity: 0.0, options: .allowUserInteraction, animations: { //presentedControllerView.center.y += containerView!.bounds.size.height }, completion: {(completed: Bool) -> Void in transitionContext.completeTransition(completed) }) } - func animateDismissalWithTransitionContext(transitionContext: UIViewControllerContextTransitioning) { - let presentedControllerView = transitionContext.viewForKey(UITransitionContextFromViewKey)! + func animateDismissalWithTransitionContext(_ transitionContext: UIViewControllerContextTransitioning) { + let presentedControllerView = transitionContext.view(forKey: UITransitionContextViewKey.from)! //let containerView = transitionContext.containerView() // Animate the presented view off the bottom of the view - UIView.animateWithDuration(self.duration, delay: 0.0, usingSpringWithDamping: 1.0, initialSpringVelocity: 0.0, options: .AllowUserInteraction, animations: { + UIView.animate(withDuration: self.duration, delay: 0.0, usingSpringWithDamping: 1.0, initialSpringVelocity: 0.0, options: .allowUserInteraction, animations: { //presentedControllerView.center.y += containerView!.bounds.size.height presentedControllerView.alpha = 0.0 }, completion: {(completed: Bool) -> Void in diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Utils/Categories/AACustomPresentationController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Utils/Categories/AACustomPresentationController.swift index 4c243b1b04..50589261e1 100755 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Utils/Categories/AACustomPresentationController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Utils/Categories/AACustomPresentationController.swift @@ -18,17 +18,17 @@ class AACustomPresentationController: UIPresentationController { // Add the dimming view and the presented view to the heirarchy self.dimmingView.frame = self.containerView!.bounds self.containerView!.addSubview(self.dimmingView) - self.containerView!.addSubview(self.presentedView()!) + self.containerView!.addSubview(self.presentedView!) // Fade in the dimming view alongside the transition - if let transitionCoordinator = self.presentingViewController.transitionCoordinator() { - transitionCoordinator.animateAlongsideTransition({(context: UIViewControllerTransitionCoordinatorContext!) -> Void in + if let transitionCoordinator = self.presentingViewController.transitionCoordinator { + transitionCoordinator.animate(alongsideTransition: {(context: UIViewControllerTransitionCoordinatorContext!) -> Void in self.dimmingView.alpha = 1.0 }, completion:nil) } } - override func presentationTransitionDidEnd(completed: Bool) { + override func presentationTransitionDidEnd(_ completed: Bool) { // If the presentation didn't complete, remove the dimming view if !completed { self.dimmingView.removeFromSuperview() @@ -37,24 +37,24 @@ class AACustomPresentationController: UIPresentationController { override func dismissalTransitionWillBegin() { // Fade out the dimming view alongside the transition - if let transitionCoordinator = self.presentingViewController.transitionCoordinator() { - transitionCoordinator.animateAlongsideTransition({(context: UIViewControllerTransitionCoordinatorContext!) -> Void in + if let transitionCoordinator = self.presentingViewController.transitionCoordinator { + transitionCoordinator.animate(alongsideTransition: {(context: UIViewControllerTransitionCoordinatorContext!) -> Void in self.dimmingView.alpha = 0.0 }, completion:nil) } } - override func dismissalTransitionDidEnd(completed: Bool) { + override func dismissalTransitionDidEnd(_ completed: Bool) { // If the dismissal completed, remove the dimming view if completed { self.dimmingView.removeFromSuperview() } } - override func frameOfPresentedViewInContainerView() -> CGRect { + override var frameOfPresentedViewInContainerView : CGRect { // We don't want the presented view to fill the whole container view, so inset it's frame var frame = self.containerView!.bounds; - frame = CGRectInset(frame, 0.0, 0.0) + frame = frame.insetBy(dx: 0.0, dy: 0.0) return frame } @@ -62,10 +62,10 @@ class AACustomPresentationController: UIPresentationController { // ---- UIContentContainer protocol methods - override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator transitionCoordinator: UIViewControllerTransitionCoordinator) { - super.viewWillTransitionToSize(size, withTransitionCoordinator: transitionCoordinator) + override func viewWillTransition(to size: CGSize, with transitionCoordinator: UIViewControllerTransitionCoordinator) { + super.viewWillTransition(to: size, with: transitionCoordinator) - transitionCoordinator.animateAlongsideTransition({(context: UIViewControllerTransitionCoordinatorContext!) -> Void in + transitionCoordinator.animate(alongsideTransition: {(context: UIViewControllerTransitionCoordinatorContext!) -> Void in self.dimmingView.frame = self.containerView!.bounds }, completion:nil) } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Utils/Extensions/AttributedLabel.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Utils/Extensions/AttributedLabel.swift index 3941996292..b4e8e3ea55 100755 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Utils/Extensions/AttributedLabel.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Utils/Extensions/AttributedLabel.swift @@ -1,82 +1,82 @@ import UIKit -public class AttributedLabel: UIView { +open class AttributedLabel: UIView { public enum ContentAlignment: Int { - case Center - case Top - case Bottom - case Left - case Right - case TopLeft - case TopRight - case BottomLeft - case BottomRight + case center + case top + case bottom + case left + case right + case topLeft + case topRight + case bottomLeft + case bottomRight - func alignOffset(viewSize viewSize: CGSize, containerSize: CGSize) -> CGPoint { + func alignOffset(viewSize: CGSize, containerSize: CGSize) -> CGPoint { let xMargin = viewSize.width - containerSize.width let yMargin = viewSize.height - containerSize.height switch self { - case Center: + case .center: return CGPoint(x: max(xMargin / 2, 0), y: max(yMargin / 2, 0)) - case Top: + case .top: return CGPoint(x: max(xMargin / 2, 0), y: 0) - case Bottom: + case .bottom: return CGPoint(x: max(xMargin / 2, 0), y: max(yMargin, 0)) - case Left: + case .left: return CGPoint(x: 0, y: max(yMargin / 2, 0)) - case Right: + case .right: return CGPoint(x: max(xMargin, 0), y: max(yMargin / 2, 0)) - case TopLeft: + case .topLeft: return CGPoint(x: 0, y: 0) - case TopRight: + case .topRight: return CGPoint(x: max(xMargin, 0), y: 0) - case BottomLeft: + case .bottomLeft: return CGPoint(x: 0, y: max(yMargin, 0)) - case BottomRight: + case .bottomRight: return CGPoint(x: max(xMargin, 0), y: max(yMargin, 0)) } } } /// default is `0`. - public var numberOfLines: Int = 0 { + open var numberOfLines: Int = 0 { didSet { setNeedsDisplay() } } /// default is `Left`. - public var contentAlignment: ContentAlignment = .Left { + open var contentAlignment: ContentAlignment = .left { didSet { setNeedsDisplay() } } /// `lineFragmentPadding` of `NSTextContainer`. default is `0`. - public var padding: CGFloat = 0 { + open var padding: CGFloat = 0 { didSet { setNeedsDisplay() } } /// default is system font 17 plain. - public var font = UIFont.systemFontOfSize(17) { + open var font = UIFont.systemFont(ofSize: 17) { didSet { setNeedsDisplay() } } /// default is `ByTruncatingTail`. - public var lineBreakMode: NSLineBreakMode = .ByTruncatingTail { + open var lineBreakMode: NSLineBreakMode = .byTruncatingTail { didSet { setNeedsDisplay() } } /// default is nil (text draws black). - public var textColor: UIColor? { + open var textColor: UIColor? { didSet { setNeedsDisplay() } } /// default is nil. - public var paragraphStyle: NSParagraphStyle? { + open var paragraphStyle: NSParagraphStyle? { didSet { setNeedsDisplay() } } /// default is nil. - public var shadow: NSShadow? { + open var shadow: NSShadow? { didSet { setNeedsDisplay() } } /// default is nil. - public var attributedText: NSAttributedString? { + open var attributedText: NSAttributedString? { didSet { setNeedsDisplay() } } /// default is nil. - public var text: String? { + open var text: String? { get { return attributedText?.string } @@ -89,7 +89,7 @@ public class AttributedLabel: UIView { } } - private var mergedAttributedText: NSAttributedString? { + fileprivate var mergedAttributedText: NSAttributedString? { if let attributedText = attributedText { return mergeAttributes(attributedText) } @@ -99,24 +99,24 @@ public class AttributedLabel: UIView { public override init(frame: CGRect) { super.init(frame: frame) - opaque = false - contentMode = .Redraw + isOpaque = false + contentMode = .redraw } public required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) - opaque = false - contentMode = .Redraw + isOpaque = false + contentMode = .redraw } - public override func setNeedsDisplay() { - if NSThread.isMainThread() { + open override func setNeedsDisplay() { + if Thread.isMainThread { super.setNeedsDisplay() } } - public override func drawRect(rect: CGRect) { + open override func draw(_ rect: CGRect) { guard let attributedText = mergedAttributedText else { return } @@ -127,15 +127,15 @@ public class AttributedLabel: UIView { let storage = NSTextStorage(attributedString: attributedText) storage.addLayoutManager(manager) - let frame = manager.usedRectForTextContainer(container) - let point = contentAlignment.alignOffset(viewSize: rect.size, containerSize: CGRectIntegral(frame).size) + let frame = manager.usedRect(for: container) + let point = contentAlignment.alignOffset(viewSize: rect.size, containerSize: frame.integral.size) - let glyphRange = manager.glyphRangeForTextContainer(container) - manager.drawBackgroundForGlyphRange(glyphRange, atPoint: point) - manager.drawGlyphsForGlyphRange(glyphRange, atPoint: point) + let glyphRange = manager.glyphRange(for: container) + manager.drawBackground(forGlyphRange: glyphRange, at: point) + manager.drawGlyphs(forGlyphRange: glyphRange, at: point) } - public override func sizeThatFits(size: CGSize) -> CGSize { + open override func sizeThatFits(_ size: CGSize) -> CGSize { guard let attributedText = mergedAttributedText else { return super.sizeThatFits(size) } @@ -146,17 +146,17 @@ public class AttributedLabel: UIView { let storage = NSTextStorage(attributedString: attributedText) storage.addLayoutManager(manager) - let frame = manager.usedRectForTextContainer(container) - return CGRectIntegral(frame).size + let frame = manager.usedRect(for: container) + return frame.integral.size } - public override func sizeToFit() { + open override func sizeToFit() { super.sizeToFit() - frame.size = sizeThatFits(CGSize(width: bounds.width, height: CGFloat.max)) + frame.size = sizeThatFits(CGSize(width: bounds.width, height: CGFloat.greatestFiniteMagnitude)) } - private func textContainer(size: CGSize) -> NSTextContainer { + fileprivate func textContainer(_ size: CGSize) -> NSTextContainer { let container = NSTextContainer(size: size) container.lineBreakMode = lineBreakMode container.lineFragmentPadding = padding @@ -164,13 +164,13 @@ public class AttributedLabel: UIView { return container } - private func layoutManager(container: NSTextContainer) -> NSLayoutManager { + fileprivate func layoutManager(_ container: NSTextContainer) -> NSLayoutManager { let layoutManager = NSLayoutManager() layoutManager.addTextContainer(container) return layoutManager } - private func mergeAttributes(attributedText: NSAttributedString) -> NSAttributedString { + fileprivate func mergeAttributes(_ attributedText: NSAttributedString) -> NSAttributedString { let attrString = NSMutableAttributedString(attributedString: attributedText) addAttribute(attrString, attrName: NSFontAttributeName, attr: font) @@ -190,9 +190,9 @@ public class AttributedLabel: UIView { return attrString } - private func addAttribute(attrString: NSMutableAttributedString, attrName: String, attr: AnyObject) { + fileprivate func addAttribute(_ attrString: NSMutableAttributedString, attrName: String, attr: AnyObject) { let range = NSRange(location: 0, length: attrString.length) - attrString.enumerateAttribute(attrName, inRange: range, options: .Reverse) { object, range, pointer in + attrString.enumerateAttribute(attrName, in: range, options: .reverse) { object, range, pointer in if object == nil { attrString.addAttributes([attrName: attr], range: range) } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Utils/Extensions/TapedLabel/AATapLabel.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Utils/Extensions/TapedLabel/AATapLabel.swift index cea3be4c8d..3a617b4151 100755 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Utils/Extensions/TapedLabel/AATapLabel.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Utils/Extensions/TapedLabel/AATapLabel.swift @@ -4,34 +4,34 @@ import Foundation -public class TapLabel: UILabel, NSLayoutManagerDelegate { +open class TapLabel: UILabel, NSLayoutManagerDelegate { - public static let LinkContentName = "TapLabelLinkContentName" - public static let SelectedForegroudColorName = "TapLabelSelectedForegroudColorName" + open static let LinkContentName = "TapLabelLinkContentName" + open static let SelectedForegroudColorName = "TapLabelSelectedForegroudColorName" - public weak var delegate: AATapLabelDelegate? + open weak var delegate: AATapLabelDelegate? - private let layoutManager = NSLayoutManager() - private let textContainer = NSTextContainer() - private let textStorage = NSTextStorage() - private var rangesForUrls = [NSRange]() - private var links = [String: NSRange]() - private var isTouchMoved = false - private var defaultSelectedForegroundColor: UIColor? + fileprivate let layoutManager = NSLayoutManager() + fileprivate let textContainer = NSTextContainer() + fileprivate let textStorage = NSTextStorage() + fileprivate var rangesForUrls = [NSRange]() + fileprivate var links = [String: NSRange]() + fileprivate var isTouchMoved = false + fileprivate var defaultSelectedForegroundColor: UIColor? - private var selected: (String, NSRange)? { + fileprivate var selected: (String, NSRange)? { didSet { if let (_, range) = selected { if let currentColor = textStorage.attribute(NSForegroundColorAttributeName, - atIndex: range.location, + at: range.location, effectiveRange: nil) as? UIColor { defaultSelectedForegroundColor = currentColor } if let color = textStorage.attribute(TapLabel.SelectedForegroudColorName, - atIndex: range.location, + at: range.location, effectiveRange: nil) as? UIColor { textStorage.addAttribute(NSForegroundColorAttributeName, value: color, range: range) @@ -48,19 +48,19 @@ public class TapLabel: UILabel, NSLayoutManagerDelegate { } } - public override var lineBreakMode: NSLineBreakMode { + open override var lineBreakMode: NSLineBreakMode { didSet { textContainer.lineBreakMode = lineBreakMode } } - public override var numberOfLines: Int { + open override var numberOfLines: Int { didSet { textContainer.maximumNumberOfLines = numberOfLines } } - public override var attributedText: NSAttributedString! { + open override var attributedText: NSAttributedString! { didSet { textStorage.setAttributedString(attributedText) updateLinks() @@ -68,13 +68,13 @@ public class TapLabel: UILabel, NSLayoutManagerDelegate { } } - public override var frame: CGRect { + open override var frame: CGRect { didSet { textContainer.size = frame.size } } - public override var bounds: CGRect { + open override var bounds: CGRect { didSet { textContainer.size = bounds.size } @@ -93,19 +93,19 @@ public class TapLabel: UILabel, NSLayoutManagerDelegate { textStorage.addLayoutManager(layoutManager) - userInteractionEnabled = true + isUserInteractionEnabled = true } public required init(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - private func updateLinks() { + fileprivate func updateLinks() { links = [String: NSRange]() attributedText.enumerateAttribute(TapLabel.LinkContentName, - inRange: NSMakeRange(0, attributedText.length), - options: NSAttributedStringEnumerationOptions(rawValue: 0)) + in: NSMakeRange(0, attributedText.length), + options: NSAttributedString.EnumerationOptions(rawValue: 0)) { value, range, stop in @@ -115,7 +115,7 @@ public class TapLabel: UILabel, NSLayoutManagerDelegate { } } - private func updateRangesForUrls() + fileprivate func updateRangesForUrls() { // var error: NSError? @@ -136,7 +136,7 @@ public class TapLabel: UILabel, NSLayoutManagerDelegate { // rangesForUrls = matches.map { $0.range } } - public override func textRectForBounds(bounds: CGRect, limitedToNumberOfLines numberOfLines: Int) -> CGRect + open override func textRect(forBounds bounds: CGRect, limitedToNumberOfLines numberOfLines: Int) -> CGRect { let savedTextContainerSize = textContainer.size let savedTextContainerNumberOfLines = textContainer.maximumNumberOfLines @@ -144,8 +144,8 @@ public class TapLabel: UILabel, NSLayoutManagerDelegate { textContainer.size = bounds.size textContainer.maximumNumberOfLines = numberOfLines - let glyphRange = layoutManager.glyphRangeForTextContainer(textContainer) - var textBounds = layoutManager.boundingRectForGlyphRange(glyphRange, inTextContainer:textContainer) + let glyphRange = layoutManager.glyphRange(for: textContainer) + var textBounds = layoutManager.boundingRect(forGlyphRange: glyphRange, in:textContainer) textBounds.origin = bounds.origin textBounds.size.width = ceil(textBounds.size.width) @@ -157,20 +157,20 @@ public class TapLabel: UILabel, NSLayoutManagerDelegate { return textBounds; } - public override func drawTextInRect(rect: CGRect) + open override func drawText(in rect: CGRect) { - let glyphRange = layoutManager.glyphRangeForTextContainer(textContainer) + let glyphRange = layoutManager.glyphRange(for: textContainer) let textOffset = calcTextOffsetForGlyphRange(glyphRange) - layoutManager.drawBackgroundForGlyphRange(glyphRange, atPoint:textOffset) - layoutManager.drawGlyphsForGlyphRange(glyphRange, atPoint:textOffset) + layoutManager.drawBackground(forGlyphRange: glyphRange, at:textOffset) + layoutManager.drawGlyphs(forGlyphRange: glyphRange, at:textOffset) } - private func calcTextOffsetForGlyphRange(glyphRange: NSRange) -> CGPoint + fileprivate func calcTextOffsetForGlyphRange(_ glyphRange: NSRange) -> CGPoint { - var textOffset = CGPointZero + var textOffset = CGPoint.zero - let textBounds = layoutManager.boundingRectForGlyphRange(glyphRange, inTextContainer:textContainer) + let textBounds = layoutManager.boundingRect(forGlyphRange: glyphRange, in:textContainer) let paddingHeight = (self.bounds.size.height - textBounds.size.height) / 2 if (paddingHeight > 0) { textOffset.y = paddingHeight; @@ -179,24 +179,24 @@ public class TapLabel: UILabel, NSLayoutManagerDelegate { return textOffset; } - private func linkAtPoint(point: CGPoint) -> (String, NSRange)? { + fileprivate func linkAtPoint(_ point: CGPoint) -> (String, NSRange)? { var point2 = point if textStorage.length == 0 { return nil } - let glyphRange = layoutManager.glyphRangeForTextContainer(textContainer) + let glyphRange = layoutManager.glyphRange(for: textContainer) let textOffset = calcTextOffsetForGlyphRange(glyphRange) point2.x = point2.x - textOffset.x point2.y = point2.y - textOffset.y - let touchedChar = layoutManager.glyphIndexForPoint(point2, inTextContainer:textContainer) + let touchedChar = layoutManager.glyphIndex(for: point2, in:textContainer) var lineRange = NSRange() - let lineRect = layoutManager.lineFragmentUsedRectForGlyphAtIndex(touchedChar, effectiveRange:&lineRange) + let lineRect = layoutManager.lineFragmentUsedRect(forGlyphAt: touchedChar, effectiveRange:&lineRange) - if !CGRectContainsPoint(lineRect, point2) { + if !lineRect.contains(point2) { return nil } @@ -212,27 +212,27 @@ public class TapLabel: UILabel, NSLayoutManagerDelegate { //MARK: - Interactions - public override func touchesBegan(touches: Set, withEvent event: UIEvent?) { + open override func touchesBegan(_ touches: Set, with event: UIEvent?) { isTouchMoved = false - let touchLocation = touches.first!.locationInView(self) + let touchLocation = touches.first!.location(in: self) if let (link, range) = linkAtPoint(touchLocation) { selected = (link, range) } else { - super.touchesBegan(touches, withEvent: event) + super.touchesBegan(touches, with: event) } } - public override func touchesMoved(touches: Set, withEvent event: UIEvent?) { - super.touchesMoved(touches, withEvent: event) + open override func touchesMoved(_ touches: Set, with event: UIEvent?) { + super.touchesMoved(touches, with: event) isTouchMoved = true selected = nil } - public override func touchesEnded(touches: Set, withEvent event: UIEvent?) { - super.touchesEnded(touches, withEvent: event) + open override func touchesEnded(_ touches: Set, with event: UIEvent?) { + super.touchesEnded(touches, with: event) if !isTouchMoved { if (selected != nil) { @@ -244,17 +244,17 @@ public class TapLabel: UILabel, NSLayoutManagerDelegate { } - public override func touchesCancelled(touches: Set?, withEvent event: UIEvent?) { - super.touchesCancelled(touches, withEvent: event) + open override func touchesCancelled(_ touches: Set, with event: UIEvent?) { + super.touchesCancelled(touches, with: event) selected = nil } //MARK: - NSLayoutManagerDelegate - @objc public func layoutManager( - layoutManager: NSLayoutManager, - shouldBreakLineByWordBeforeCharacterAtIndex charIndex: Int) -> Bool + @objc open func layoutManager( + _ layoutManager: NSLayoutManager, + shouldBreakLineByWordBeforeCharacterAt charIndex: Int) -> Bool { for range in rangesForUrls { if range.location < charIndex && charIndex < range.location + range.length { @@ -264,4 +264,4 @@ public class TapLabel: UILabel, NSLayoutManagerDelegate { return true } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Utils/Extensions/TapedLabel/AATapLabelDelegate.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Utils/Extensions/TapedLabel/AATapLabelDelegate.swift index a63c81d6c0..b00bb06c28 100755 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Utils/Extensions/TapedLabel/AATapLabelDelegate.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Utils/Extensions/TapedLabel/AATapLabelDelegate.swift @@ -6,6 +6,6 @@ import Foundation public protocol AATapLabelDelegate: class { - func tapLabel(tapLabel: TapLabel, didSelectLink link: String) + func tapLabel(_ tapLabel: TapLabel, didSelectLink link: String) -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Utils/Reachability.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Utils/Reachability.swift index a42432b8ae..8cf8d1c85b 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Utils/Reachability.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Utils/Reachability.swift @@ -1,388 +1,389 @@ -/* -Copyright (c) 2014, Ashley Mills -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this -list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation -and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. -*/ - -import SystemConfiguration -import Foundation - -enum ReachabilityError: ErrorType { - case FailedToCreateWithAddress(sockaddr_in) - case FailedToCreateWithHostname(String) - case UnableToSetCallback - case UnableToSetDispatchQueue -} - -public let ReachabilityChangedNotification = "ReachabilityChangedNotification" - -func callback(reachability:SCNetworkReachability, flags: SCNetworkReachabilityFlags, info: UnsafeMutablePointer) { - let reachability = Unmanaged.fromOpaque(COpaquePointer(info)).takeUnretainedValue() - - dispatch_async(dispatch_get_main_queue()) { - reachability.reachabilityChanged(flags) - } -} - - -public class Reachability: NSObject { - - public typealias NetworkReachable = (Reachability) -> () - public typealias NetworkUnreachable = (Reachability) -> () - - public enum NetworkStatus: CustomStringConvertible { - - case NotReachable, ReachableViaWiFi, ReachableViaWWAN - - public var description: String { - switch self { - case .ReachableViaWWAN: - return "Cellular" - case .ReachableViaWiFi: - return "WiFi" - case .NotReachable: - return "No Connection" - } - } - } - - // MARK: - *** Public properties *** - - public var whenReachable: NetworkReachable? - public var whenUnreachable: NetworkUnreachable? - public var reachableOnWWAN: Bool - public var notificationCenter = NSNotificationCenter.defaultCenter() - - public var currentReachabilityStatus: NetworkStatus { - if isReachable() { - if isReachableViaWiFi() { - return .ReachableViaWiFi - } - if isRunningOnDevice { - return .ReachableViaWWAN - } - } - - return .NotReachable - } - - public var currentReachabilityString: String { - return "\(currentReachabilityStatus)" - } - - // MARK: - *** Initialisation methods *** - - required public init(reachabilityRef: SCNetworkReachability) { - reachableOnWWAN = true - self.reachabilityRef = reachabilityRef - } - - public convenience init(hostname: String) throws { - - let nodename = (hostname as NSString).UTF8String - guard let ref = SCNetworkReachabilityCreateWithName(nil, nodename) else { throw ReachabilityError.FailedToCreateWithHostname(hostname) } - - self.init(reachabilityRef: ref) - } - - public class func reachabilityForInternetConnection() throws -> Reachability { - - var zeroAddress = sockaddr_in() - zeroAddress.sin_len = UInt8(sizeofValue(zeroAddress)) - zeroAddress.sin_family = sa_family_t(AF_INET) - - guard let ref = withUnsafePointer(&zeroAddress, { - SCNetworkReachabilityCreateWithAddress(nil, UnsafePointer($0)) - }) else { throw ReachabilityError.FailedToCreateWithAddress(zeroAddress) } - - return Reachability(reachabilityRef: ref) - } - - public class func reachabilityForLocalWiFi() throws -> Reachability { - - var localWifiAddress: sockaddr_in = sockaddr_in(sin_len: __uint8_t(0), sin_family: sa_family_t(0), sin_port: in_port_t(0), sin_addr: in_addr(s_addr: 0), sin_zero: (0, 0, 0, 0, 0, 0, 0, 0)) - localWifiAddress.sin_len = UInt8(sizeofValue(localWifiAddress)) - localWifiAddress.sin_family = sa_family_t(AF_INET) - - // IN_LINKLOCALNETNUM is defined in as 169.254.0.0 - let address: UInt32 = 0xA9FE0000 - localWifiAddress.sin_addr.s_addr = in_addr_t(address.bigEndian) - - guard let ref = withUnsafePointer(&localWifiAddress, { - SCNetworkReachabilityCreateWithAddress(nil, UnsafePointer($0)) - }) else { throw ReachabilityError.FailedToCreateWithAddress(localWifiAddress) } - - return Reachability(reachabilityRef: ref) - } - - // MARK: - *** Notifier methods *** - public func startNotifier() throws { - - if notifierRunning { return } - - var context = SCNetworkReachabilityContext(version: 0, info: nil, retain: nil, release: nil, copyDescription: nil) - context.info = UnsafeMutablePointer(Unmanaged.passUnretained(self).toOpaque()) - - if !SCNetworkReachabilitySetCallback(reachabilityRef!, callback, &context) { - stopNotifier() - throw ReachabilityError.UnableToSetCallback - } - - if !SCNetworkReachabilitySetDispatchQueue(reachabilityRef!, reachabilitySerialQueue) { - stopNotifier() - throw ReachabilityError.UnableToSetDispatchQueue - } - - notifierRunning = true - } - - - public func stopNotifier() { - if let reachabilityRef = reachabilityRef { - SCNetworkReachabilitySetCallback(reachabilityRef, nil, nil) - SCNetworkReachabilitySetDispatchQueue(reachabilityRef, nil) - } - notifierRunning = false - } - - // MARK: - *** Connection test methods *** - public func isReachable() -> Bool { - return isReachableWithTest({ (flags: SCNetworkReachabilityFlags) -> (Bool) in - return self.isReachableWithFlags(flags) - }) - } - - public func isReachableViaWWAN() -> Bool { - - if isRunningOnDevice { - return isReachableWithTest() { flags -> Bool in - // Check we're REACHABLE - if self.isReachable(flags) { - - // Now, check we're on WWAN - if self.isOnWWAN(flags) { - return true - } - } - return false - } - } - return false - } - - public func isReachableViaWiFi() -> Bool { - - return isReachableWithTest() { flags -> Bool in - - // Check we're reachable - if self.isReachable(flags) { - - if self.isRunningOnDevice { - // Check we're NOT on WWAN - if self.isOnWWAN(flags) { - return false - } - } - return true - } - - return false - } - } - - // MARK: - *** Private methods *** - private var isRunningOnDevice: Bool = { - #if (arch(i386) || arch(x86_64)) && os(iOS) - return false - #else - return true - #endif - }() - - private var notifierRunning = false - private var reachabilityRef: SCNetworkReachability? - private let reachabilitySerialQueue = dispatch_queue_create("uk.co.ashleymills.reachability", DISPATCH_QUEUE_SERIAL) - - private func reachabilityChanged(flags: SCNetworkReachabilityFlags) { - if isReachableWithFlags(flags) { - if let block = whenReachable { - block(self) - } - } else { - if let block = whenUnreachable { - block(self) - } - } - - notificationCenter.postNotificationName(ReachabilityChangedNotification, object:self) - } - - private func isReachableWithFlags(flags: SCNetworkReachabilityFlags) -> Bool { - - let reachable = isReachable(flags) - - if !reachable { - return false - } - - if isConnectionRequiredOrTransient(flags) { - return false - } - - if isRunningOnDevice { - if isOnWWAN(flags) && !reachableOnWWAN { - // We don't want to connect when on 3G. - return false - } - } - - return true - } - - private func isReachableWithTest(test: (SCNetworkReachabilityFlags) -> (Bool)) -> Bool { - - if let reachabilityRef = reachabilityRef { - - var flags = SCNetworkReachabilityFlags(rawValue: 0) - let gotFlags = withUnsafeMutablePointer(&flags) { - SCNetworkReachabilityGetFlags(reachabilityRef, UnsafeMutablePointer($0)) - } - - if gotFlags { - return test(flags) - } - } - - return false - } - - // WWAN may be available, but not active until a connection has been established. - // WiFi may require a connection for VPN on Demand. - private func isConnectionRequired() -> Bool { - return connectionRequired() - } - - private func connectionRequired() -> Bool { - return isReachableWithTest({ (flags: SCNetworkReachabilityFlags) -> (Bool) in - return self.isConnectionRequired(flags) - }) - } - - // Dynamic, on demand connection? - private func isConnectionOnDemand() -> Bool { - return isReachableWithTest({ (flags: SCNetworkReachabilityFlags) -> (Bool) in - return self.isConnectionRequired(flags) && self.isConnectionOnTrafficOrDemand(flags) - }) - } - - // Is user intervention required? - private func isInterventionRequired() -> Bool { - return isReachableWithTest({ (flags: SCNetworkReachabilityFlags) -> (Bool) in - return self.isConnectionRequired(flags) && self.isInterventionRequired(flags) - }) - } - - private func isOnWWAN(flags: SCNetworkReachabilityFlags) -> Bool { - #if os(iOS) - return flags.contains(.IsWWAN) - #else - return false - #endif - } - private func isReachable(flags: SCNetworkReachabilityFlags) -> Bool { - return flags.contains(.Reachable) - } - private func isConnectionRequired(flags: SCNetworkReachabilityFlags) -> Bool { - return flags.contains(.ConnectionRequired) - } - private func isInterventionRequired(flags: SCNetworkReachabilityFlags) -> Bool { - return flags.contains(.InterventionRequired) - } - private func isConnectionOnTraffic(flags: SCNetworkReachabilityFlags) -> Bool { - return flags.contains(.ConnectionOnTraffic) - } - private func isConnectionOnDemand(flags: SCNetworkReachabilityFlags) -> Bool { - return flags.contains(.ConnectionOnDemand) - } - func isConnectionOnTrafficOrDemand(flags: SCNetworkReachabilityFlags) -> Bool { - return !flags.intersect([.ConnectionOnTraffic, .ConnectionOnDemand]).isEmpty - } - private func isTransientConnection(flags: SCNetworkReachabilityFlags) -> Bool { - return flags.contains(.TransientConnection) - } - private func isLocalAddress(flags: SCNetworkReachabilityFlags) -> Bool { - return flags.contains(.IsLocalAddress) - } - private func isDirect(flags: SCNetworkReachabilityFlags) -> Bool { - return flags.contains(.IsDirect) - } - private func isConnectionRequiredOrTransient(flags: SCNetworkReachabilityFlags) -> Bool { - let testcase:SCNetworkReachabilityFlags = [.ConnectionRequired, .TransientConnection] - return flags.intersect(testcase) == testcase - } - - private var reachabilityFlags: SCNetworkReachabilityFlags { - if let reachabilityRef = reachabilityRef { - - var flags = SCNetworkReachabilityFlags(rawValue: 0) - let gotFlags = withUnsafeMutablePointer(&flags) { - SCNetworkReachabilityGetFlags(reachabilityRef, UnsafeMutablePointer($0)) - } - - if gotFlags { - return flags - } - } - - return [] - } - - override public var description: String { - - var W: String - if isRunningOnDevice { - W = isOnWWAN(reachabilityFlags) ? "W" : "-" - } else { - W = "X" - } - let R = isReachable(reachabilityFlags) ? "R" : "-" - let c = isConnectionRequired(reachabilityFlags) ? "c" : "-" - let t = isTransientConnection(reachabilityFlags) ? "t" : "-" - let i = isInterventionRequired(reachabilityFlags) ? "i" : "-" - let C = isConnectionOnTraffic(reachabilityFlags) ? "C" : "-" - let D = isConnectionOnDemand(reachabilityFlags) ? "D" : "-" - let l = isLocalAddress(reachabilityFlags) ? "l" : "-" - let d = isDirect(reachabilityFlags) ? "d" : "-" - - return "\(W)\(R) \(c)\(t)\(i)\(C)\(D)\(l)\(d)" - } - - deinit { - stopNotifier() - - reachabilityRef = nil - whenReachable = nil - whenUnreachable = nil - } -} \ No newline at end of file +///* +//Copyright (c) 2014, Ashley Mills +//All rights reserved. +// +//Redistribution and use in source and binary forms, with or without +//modification, are permitted provided that the following conditions are met: +// +//1. Redistributions of source code must retain the above copyright notice, this +//list of conditions and the following disclaimer. +// +//2. Redistributions in binary form must reproduce the above copyright notice, +//this list of conditions and the following disclaimer in the documentation +//and/or other materials provided with the distribution. +// +//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +//AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +//IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +//ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +//LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +//CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +//SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +//INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +//CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +//ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +//POSSIBILITY OF SUCH DAMAGE. +//*/ +// +//import SystemConfiguration +//import Foundation +// +//enum ReachabilityError: Error { +// case failedToCreateWithAddress(sockaddr_in) +// case failedToCreateWithHostname(String) +// case unableToSetCallback +// case unableToSetDispatchQueue +//} +// +//public let ReachabilityChangedNotification = "ReachabilityChangedNotification" +// +//func callback(_ reachability:SCNetworkReachability, flags: SCNetworkReachabilityFlags, info: UnsafeMutableRawPointer) { +//// let reachability = Unmanaged.fromOpaque(OpaquePointer(info)).takeUnretainedValue() +//// +//// DispatchQueue.main.async { +//// reachability.reachabilityChanged(flags) +//// } +//} +// +// +//open class Reachability: NSObject { +// +// public typealias NetworkReachable = (Reachability) -> () +// public typealias NetworkUnreachable = (Reachability) -> () +// +// public enum NetworkStatus: CustomStringConvertible { +// +// case notReachable, reachableViaWiFi, reachableViaWWAN +// +// public var description: String { +// switch self { +// case .reachableViaWWAN: +// return "Cellular" +// case .reachableViaWiFi: +// return "WiFi" +// case .notReachable: +// return "No Connection" +// } +// } +// } +// +// // MARK: - *** Public properties *** +// +// open var whenReachable: NetworkReachable? +// open var whenUnreachable: NetworkUnreachable? +// open var reachableOnWWAN: Bool +// open var notificationCenter = NotificationCenter.default +// +// open var currentReachabilityStatus: NetworkStatus { +// if isReachable() { +// if isReachableViaWiFi() { +// return .reachableViaWiFi +// } +// if isRunningOnDevice { +// return .reachableViaWWAN +// } +// } +// +// return .notReachable +// } +// +// open var currentReachabilityString: String { +// return "\(currentReachabilityStatus)" +// } +// +// // MARK: - *** Initialisation methods *** +// +// required public init(reachabilityRef: SCNetworkReachability) { +// reachableOnWWAN = true +// self.reachabilityRef = reachabilityRef +// } +// +// public convenience init(hostname: String) throws { +// +// let nodename = (hostname as NSString).utf8String +// guard let ref = SCNetworkReachabilityCreateWithName(nil, nodename!) else { throw ReachabilityError.failedToCreateWithHostname(hostname) } +// +// self.init(reachabilityRef: ref) +// } +// +// open class func reachabilityForInternetConnection() throws -> Reachability { +// +// var zeroAddress = sockaddr_in() +// zeroAddress.sin_len = UInt8(MemoryLayout.size(ofValue: zeroAddress)) +// zeroAddress.sin_family = sa_family_t(AF_INET) +// +//// guard let ref = withUnsafePointer(to: &zeroAddress, { +//// SCNetworkReachabilityCreateWithAddress(nil, UnsafePointer($0)) +//// }) else { throw ReachabilityError.failedToCreateWithAddress(zeroAddress) } +// +// // return Reachability(reachabilityRef: ref) +// +// } +// +// open class func reachabilityForLocalWiFi() throws -> Reachability { +// +// var localWifiAddress: sockaddr_in = sockaddr_in(sin_len: __uint8_t(0), sin_family: sa_family_t(0), sin_port: in_port_t(0), sin_addr: in_addr(s_addr: 0), sin_zero: (0, 0, 0, 0, 0, 0, 0, 0)) +// localWifiAddress.sin_len = UInt8(MemoryLayout.size(ofValue: localWifiAddress)) +// localWifiAddress.sin_family = sa_family_t(AF_INET) +// +// // IN_LINKLOCALNETNUM is defined in as 169.254.0.0 +// let address: UInt32 = 0xA9FE0000 +// localWifiAddress.sin_addr.s_addr = in_addr_t(address.bigEndian) +// +//// guard let ref = withUnsafePointer(to: &localWifiAddress, { +//// SCNetworkReachabilityCreateWithAddress(nil, UnsafePointer($0)) +//// }) else { throw ReachabilityError.failedToCreateWithAddress(localWifiAddress) } +// +// // return Reachability(reachabilityRef: ref) +// } +// +// // MARK: - *** Notifier methods *** +// open func startNotifier() throws { +// +// if notifierRunning { return } +// +//// var context = SCNetworkReachabilityContext(version: 0, info: nil, retain: nil, release: nil, copyDescription: nil) +//// context.info = UnsafeMutablePointer(Unmanaged.passUnretained(self).toOpaque()) +//// +//// if !SCNetworkReachabilitySetCallback(reachabilityRef!, callback, &context) { +//// stopNotifier() +//// throw ReachabilityError.unableToSetCallback +//// } +//// +//// if !SCNetworkReachabilitySetDispatchQueue(reachabilityRef!, reachabilitySerialQueue) { +//// stopNotifier() +//// throw ReachabilityError.unableToSetDispatchQueue +//// } +// +// notifierRunning = true +// } +// +// +// open func stopNotifier() { +// if let reachabilityRef = reachabilityRef { +// SCNetworkReachabilitySetCallback(reachabilityRef, nil, nil) +// SCNetworkReachabilitySetDispatchQueue(reachabilityRef, nil) +// } +// notifierRunning = false +// } +// +// // MARK: - *** Connection test methods *** +// open func isReachable() -> Bool { +// return isReachableWithTest({ (flags: SCNetworkReachabilityFlags) -> (Bool) in +// return self.isReachableWithFlags(flags) +// }) +// } +// +// open func isReachableViaWWAN() -> Bool { +// +// if isRunningOnDevice { +// return isReachableWithTest() { flags -> Bool in +// // Check we're REACHABLE +// if self.isReachable(flags) { +// +// // Now, check we're on WWAN +// if self.isOnWWAN(flags) { +// return true +// } +// } +// return false +// } +// } +// return false +// } +// +// open func isReachableViaWiFi() -> Bool { +// +// return isReachableWithTest() { flags -> Bool in +// +// // Check we're reachable +// if self.isReachable(flags) { +// +// if self.isRunningOnDevice { +// // Check we're NOT on WWAN +// if self.isOnWWAN(flags) { +// return false +// } +// } +// return true +// } +// +// return false +// } +// } +// +// // MARK: - *** Private methods *** +// fileprivate var isRunningOnDevice: Bool = { +// #if (arch(i386) || arch(x86_64)) && os(iOS) +// return false +// #else +// return true +// #endif +// }() +// +// fileprivate var notifierRunning = false +// fileprivate var reachabilityRef: SCNetworkReachability? +// fileprivate let reachabilitySerialQueue = DispatchQueue(label: "uk.co.ashleymills.reachability", attributes: []) +// +// fileprivate func reachabilityChanged(_ flags: SCNetworkReachabilityFlags) { +// if isReachableWithFlags(flags) { +// if let block = whenReachable { +// block(self) +// } +// } else { +// if let block = whenUnreachable { +// block(self) +// } +// } +// +// notificationCenter.post(name: Notification.Name(rawValue: ReachabilityChangedNotification), object:self) +// } +// +// fileprivate func isReachableWithFlags(_ flags: SCNetworkReachabilityFlags) -> Bool { +// +// let reachable = isReachable(flags) +// +// if !reachable { +// return false +// } +// +// if isConnectionRequiredOrTransient(flags) { +// return false +// } +// +// if isRunningOnDevice { +// if isOnWWAN(flags) && !reachableOnWWAN { +// // We don't want to connect when on 3G. +// return false +// } +// } +// +// return true +// } +// +// fileprivate func isReachableWithTest(_ test: (SCNetworkReachabilityFlags) -> (Bool)) -> Bool { +// +// if let reachabilityRef = reachabilityRef { +// +// var flags = SCNetworkReachabilityFlags(rawValue: 0) +// let gotFlags = withUnsafeMutablePointer(to: &flags) { +// SCNetworkReachabilityGetFlags(reachabilityRef, UnsafeMutablePointer($0)) +// } +// +// if gotFlags { +// return test(flags) +// } +// } +// +// return false +// } +// +// // WWAN may be available, but not active until a connection has been established. +// // WiFi may require a connection for VPN on Demand. +// fileprivate func isConnectionRequired() -> Bool { +// return connectionRequired() +// } +// +// fileprivate func connectionRequired() -> Bool { +// return isReachableWithTest({ (flags: SCNetworkReachabilityFlags) -> (Bool) in +// return self.isConnectionRequired(flags) +// }) +// } +// +// // Dynamic, on demand connection? +// fileprivate func isConnectionOnDemand() -> Bool { +// return isReachableWithTest({ (flags: SCNetworkReachabilityFlags) -> (Bool) in +// return self.isConnectionRequired(flags) && self.isConnectionOnTrafficOrDemand(flags) +// }) +// } +// +// // Is user intervention required? +// fileprivate func isInterventionRequired() -> Bool { +// return isReachableWithTest({ (flags: SCNetworkReachabilityFlags) -> (Bool) in +// return self.isConnectionRequired(flags) && self.isInterventionRequired(flags) +// }) +// } +// +// fileprivate func isOnWWAN(_ flags: SCNetworkReachabilityFlags) -> Bool { +// #if os(iOS) +// return flags.contains(.isWWAN) +// #else +// return false +// #endif +// } +// fileprivate func isReachable(_ flags: SCNetworkReachabilityFlags) -> Bool { +// return flags.contains(.reachable) +// } +// fileprivate func isConnectionRequired(_ flags: SCNetworkReachabilityFlags) -> Bool { +// return flags.contains(.connectionRequired) +// } +// fileprivate func isInterventionRequired(_ flags: SCNetworkReachabilityFlags) -> Bool { +// return flags.contains(.interventionRequired) +// } +// fileprivate func isConnectionOnTraffic(_ flags: SCNetworkReachabilityFlags) -> Bool { +// return flags.contains(.connectionOnTraffic) +// } +// fileprivate func isConnectionOnDemand(_ flags: SCNetworkReachabilityFlags) -> Bool { +// return flags.contains(.connectionOnDemand) +// } +// func isConnectionOnTrafficOrDemand(_ flags: SCNetworkReachabilityFlags) -> Bool { +// return !flags.intersection([.connectionOnTraffic, .connectionOnDemand]).isEmpty +// } +// fileprivate func isTransientConnection(_ flags: SCNetworkReachabilityFlags) -> Bool { +// return flags.contains(.transientConnection) +// } +// fileprivate func isLocalAddress(_ flags: SCNetworkReachabilityFlags) -> Bool { +// return flags.contains(.isLocalAddress) +// } +// fileprivate func isDirect(_ flags: SCNetworkReachabilityFlags) -> Bool { +// return flags.contains(.isDirect) +// } +// fileprivate func isConnectionRequiredOrTransient(_ flags: SCNetworkReachabilityFlags) -> Bool { +// let testcase:SCNetworkReachabilityFlags = [.connectionRequired, .transientConnection] +// return flags.intersection(testcase) == testcase +// } +// +// fileprivate var reachabilityFlags: SCNetworkReachabilityFlags { +// if let reachabilityRef = reachabilityRef { +// +// var flags = SCNetworkReachabilityFlags(rawValue: 0) +// let gotFlags = withUnsafeMutablePointer(to: &flags) { +// SCNetworkReachabilityGetFlags(reachabilityRef, UnsafeMutablePointer($0)) +// } +// +// if gotFlags { +// return flags +// } +// } +// +// return [] +// } +// +// override open var description: String { +// +// var W: String +// if isRunningOnDevice { +// W = isOnWWAN(reachabilityFlags) ? "W" : "-" +// } else { +// W = "X" +// } +// let R = isReachable(reachabilityFlags) ? "R" : "-" +// let c = isConnectionRequired(reachabilityFlags) ? "c" : "-" +// let t = isTransientConnection(reachabilityFlags) ? "t" : "-" +// let i = isInterventionRequired(reachabilityFlags) ? "i" : "-" +// let C = isConnectionOnTraffic(reachabilityFlags) ? "C" : "-" +// let D = isConnectionOnDemand(reachabilityFlags) ? "D" : "-" +// let l = isLocalAddress(reachabilityFlags) ? "l" : "-" +// let d = isDirect(reachabilityFlags) ? "d" : "-" +// +// return "\(W)\(R) \(c)\(t)\(i)\(C)\(D)\(l)\(d)" +// } +// +// deinit { +// stopNotifier() +// +// reachabilityRef = nil +// whenReachable = nil +// whenUnreachable = nil +// } +//} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Utils/Telephony.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Utils/Telephony.swift index ade125420c..c3894ece82 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Utils/Telephony.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Utils/Telephony.swift @@ -11,7 +11,7 @@ class AATelephony { let networkInfo = CTTelephonyNetworkInfo() let carrier = networkInfo.subscriberCellularProvider - let countryCode = carrier?.isoCountryCode?.lowercaseString + let countryCode = carrier?.isoCountryCode?.lowercased() if countryCode == nil { return "en" @@ -20,9 +20,9 @@ class AATelephony { } } - static func getCountry(iso: String) -> CountryDesc { + static func getCountry(_ iso: String) -> CountryDesc { for i in AATelephony.countryCodes { - if i.iso.lowercaseString == iso.lowercaseString { + if i.iso.lowercased() == iso.lowercased() { return i } } @@ -276,4 +276,4 @@ class CountryDesc { self.iso = iso self.country = country } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/AAAvatarView.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/AAAvatarView.swift index 5d98d5d971..db276c1a92 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/AAAvatarView.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/AAAvatarView.swift @@ -5,24 +5,24 @@ import UIKit import YYImage -public class AAAvatarView: UIView, YYAsyncLayerDelegate, ACFileEventCallback { +open class AAAvatarView: UIView, YYAsyncLayerDelegate, ACFileEventCallback { - private var title: String? - private var id: Int? - private var file: ACFileReference? - private var fileName: String? - private var showPlaceholder: Bool = false + fileprivate var title: String? + fileprivate var id: Int? + fileprivate var file: ACFileReference? + fileprivate var fileName: String? + fileprivate var showPlaceholder: Bool = false public init() { - super.init(frame: CGRectZero) + super.init(frame: CGRect.zero) self.layer.delegate = self - self.layer.contentsScale = UIScreen.mainScreen().scale - self.backgroundColor = UIColor.clearColor() - self.opaque = false - self.contentMode = .Redraw; + self.layer.contentsScale = UIScreen.main.scale + self.backgroundColor = UIColor.clear + self.isOpaque = false + self.contentMode = .redraw; if Actor.isLoggedIn() { - Actor.subscribeToDownloads(self) + Actor.subscribe(toDownloads: self) } } @@ -37,7 +37,7 @@ public class AAAvatarView: UIView, YYAsyncLayerDelegate, ACFileEventCallback { } } - public func onDownloadedWithLong(fileId: jlong) { + open func onDownloaded(withLong fileId: jlong) { if self.file?.getFileId() == fileId { dispatchOnUi { if self.file?.getFileId() == fileId { @@ -51,7 +51,7 @@ public class AAAvatarView: UIView, YYAsyncLayerDelegate, ACFileEventCallback { // Databinding // - public func bind(title: String, id: Int, fileName: String?) { + open func bind(_ title: String, id: Int, fileName: String?) { self.title = title self.id = id @@ -63,7 +63,7 @@ public class AAAvatarView: UIView, YYAsyncLayerDelegate, ACFileEventCallback { self.layer.setNeedsDisplay() } - public func bind(title: String, id: Int, avatar: ACAvatar?) { + open func bind(_ title: String, id: Int, avatar: ACAvatar?) { if self.title == title && self.id == id @@ -87,7 +87,7 @@ public class AAAvatarView: UIView, YYAsyncLayerDelegate, ACFileEventCallback { self.layer.setNeedsDisplay() } - public func unbind() { + open func unbind() { self.title = nil self.id = nil @@ -98,11 +98,11 @@ public class AAAvatarView: UIView, YYAsyncLayerDelegate, ACFileEventCallback { self.layer.setNeedsDisplay() } - public override class func layerClass() -> AnyClass { + open override class var layerClass : AnyClass { return YYAsyncLayer.self } - public func newAsyncDisplayTask() -> YYAsyncLayerDisplayTask { + open func newAsyncDisplayTask() -> YYAsyncLayerDisplayTask { let res = YYAsyncLayerDisplayTask() let _id = id @@ -118,7 +118,7 @@ public class AAAvatarView: UIView, YYAsyncLayerDelegate, ACFileEventCallback { if _fileName != nil { filePath = _fileName } else if _file != nil { - let desc = Actor.findDownloadedDescriptorWithFileId(_file!.getFileId()) + let desc = Actor.findDownloadedDescriptor(withFileId: _file!.getFileId()) if isCancelled() { return } @@ -126,7 +126,7 @@ public class AAAvatarView: UIView, YYAsyncLayerDelegate, ACFileEventCallback { filePath = CocoaFiles.pathFromDescriptor(desc!) } else { // Request if not available - Actor.startDownloadingWithReference(_file!) + Actor.startDownloading(with: _file!) filePath = nil } } else { @@ -141,19 +141,19 @@ public class AAAvatarView: UIView, YYAsyncLayerDelegate, ACFileEventCallback { if filePath == nil && _showPlaceholder && _id != nil && _title != nil { let colors = ActorSDK.sharedActor().style.avatarColors - let color = colors[_id! % colors.count].CGColor + let color = colors[_id! % colors.count].cgColor // Background - CGContextSetFillColorWithColor(context, color) + context.setFillColor(color) - CGContextAddArc(context, r, r, r, CGFloat(M_PI * 0), CGFloat(M_PI * 2), 0) + // context.addArc(r, r, r, CGFloat(M_PI * 0), CGFloat(M_PI * 2), 0) if isCancelled() { return } - CGContextDrawPath(context, .Fill) + context.drawPath(using: .fill) if isCancelled() { return @@ -161,23 +161,23 @@ public class AAAvatarView: UIView, YYAsyncLayerDelegate, ACFileEventCallback { // Text - UIColor.whiteColor().set() + UIColor.white.set() if isCancelled() { return } - let font = UIFont.systemFontOfSize(r) - var rect = CGRectMake(0, 0, r * 2, r * 2) + let font = UIFont.systemFont(ofSize: r) + var rect = CGRect(x: 0, y: 0, width: r * 2, height: r * 2) rect.origin.y = round(CGFloat(r * 2 * 47 / 100) - font.pointSize / 2) - let style : NSMutableParagraphStyle = NSParagraphStyle.defaultParagraphStyle().mutableCopy() as! NSMutableParagraphStyle - style.alignment = NSTextAlignment.Center - style.lineBreakMode = NSLineBreakMode.ByWordWrapping + let style : NSMutableParagraphStyle = NSParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle + style.alignment = NSTextAlignment.center + style.lineBreakMode = NSLineBreakMode.byWordWrapping let short = _title!.trim().smallValue() - short.drawInRect(rect, withAttributes: [NSParagraphStyleAttributeName:style, NSFontAttributeName:font, + short.draw(in: rect, withAttributes: [NSParagraphStyleAttributeName:style, NSFontAttributeName:font, NSForegroundColorAttributeName:ActorSDK.sharedActor().style.avatarTextColor]) if isCancelled() { @@ -194,25 +194,25 @@ public class AAAvatarView: UIView, YYAsyncLayerDelegate, ACFileEventCallback { if image != nil { // Background - UIBezierPath(roundedRect: CGRectMake(0, 0, r * 2, r * 2), cornerRadius: r).addClip() + UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: r * 2, height: r * 2), cornerRadius: r).addClip() if isCancelled() { return } - image!.drawInRect(CGRectMake(0, 0, r * 2, r * 2)) + image!.draw(in: CGRect(x: 0, y: 0, width: r * 2, height: r * 2)) } else { // Clean BG - CGContextSetFillColorWithColor(context, UIColor.whiteColor().CGColor) + context.setFillColor(UIColor.white.cgColor) - CGContextAddArc(context, r, r, r, CGFloat(M_PI * 0), CGFloat(M_PI * 2), 0) + // context.addArc(r, r, r, CGFloat(M_PI * 0), CGFloat(M_PI * 2), 0) if isCancelled() { return } - CGContextDrawPath(context, .Fill) + context.drawPath(using: .fill) } if isCancelled() { @@ -220,19 +220,19 @@ public class AAAvatarView: UIView, YYAsyncLayerDelegate, ACFileEventCallback { } } else { // Clean BG - CGContextSetFillColorWithColor(context, UIColor.whiteColor().CGColor) + context.setFillColor(UIColor.white.cgColor) if isCancelled() { return } - CGContextAddArc(context, r, r, r, CGFloat(M_PI * 0), CGFloat(M_PI * 2), 0) + // context.addArc(r, r, r, CGFloat(M_PI * 0), CGFloat(M_PI * 2), 0) if isCancelled() { return } - CGContextDrawPath(context, .Fill) + context.drawPath(using: .fill) if isCancelled() { return @@ -241,19 +241,19 @@ public class AAAvatarView: UIView, YYAsyncLayerDelegate, ACFileEventCallback { // Border - CGContextSetStrokeColorWithColor(context, UIColor(red: 0, green: 0, blue: 0, alpha: 0x10/255.0).CGColor) + context.setStrokeColor(UIColor(red: 0, green: 0, blue: 0, alpha: 0x10/255.0).cgColor) if isCancelled() { return } - CGContextAddArc(context, r, r, r, CGFloat(M_PI * 0), CGFloat(M_PI * 2), 0) + // context.addArc(r, r, r, CGFloat(M_PI * 0), CGFloat(M_PI * 2), 0) if isCancelled() { return } - CGContextDrawPath(context, .Stroke) + context.drawPath(using: .stroke) } return res } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/AABigPlaceholderView.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/AABigPlaceholderView.swift index eb093eaf77..6b931ce7d4 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/AABigPlaceholderView.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/AABigPlaceholderView.swift @@ -6,33 +6,33 @@ import UIKit class AABigPlaceholderView: UIView { - private var contentView: UIView! - private var bgView: UIView! - private var imageView: UIImageView! - private var titleLabel: UILabel! - private var subtitleLabel: UILabel! - private var actionButton: UIButton! - private var topOffset: CGFloat! - private var subtitle2Label: UILabel! - private var action2Button: UIButton! + fileprivate var contentView: UIView! + fileprivate var bgView: UIView! + fileprivate var imageView: UIImageView! + fileprivate var titleLabel: UILabel! + fileprivate var subtitleLabel: UILabel! + fileprivate var actionButton: UIButton! + fileprivate var topOffset: CGFloat! + fileprivate var subtitle2Label: UILabel! + fileprivate var action2Button: UIButton! // // Constructors // init(topOffset: CGFloat!) { - super.init(frame: CGRectZero) + super.init(frame: CGRect.zero) self.topOffset = topOffset - backgroundColor = UIColor.whiteColor() + backgroundColor = UIColor.white contentView = UIView() - contentView.backgroundColor = UIColor.whiteColor() + contentView.backgroundColor = UIColor.white addSubview(contentView) imageView = UIImageView() - imageView.hidden = true + imageView.isHidden = true bgView = UIView() bgView.backgroundColor = ActorSDK.sharedActor().style.placeholderBgColor @@ -41,31 +41,31 @@ class AABigPlaceholderView: UIView { titleLabel = UILabel() titleLabel.textColor = ActorSDK.sharedActor().style.placeholderTitleColor - titleLabel.font = UIFont.systemFontOfSize(22) - titleLabel.textAlignment = NSTextAlignment.Center + titleLabel.font = UIFont.systemFont(ofSize: 22) + titleLabel.textAlignment = NSTextAlignment.center titleLabel.text = " " titleLabel.sizeToFit() contentView.addSubview(titleLabel) subtitleLabel = UILabel() subtitleLabel.textColor = ActorSDK.sharedActor().style.placeholderHintColor - subtitleLabel.font = UIFont.systemFontOfSize(16.0) - subtitleLabel.textAlignment = NSTextAlignment.Center + subtitleLabel.font = UIFont.systemFont(ofSize: 16.0) + subtitleLabel.textAlignment = NSTextAlignment.center subtitleLabel.numberOfLines = 0 contentView.addSubview(subtitleLabel) - actionButton = UIButton(type: .System) + actionButton = UIButton(type: .system) actionButton.titleLabel!.font = UIFont.mediumSystemFontOfSize(21) contentView.addSubview(actionButton) subtitle2Label = UILabel() subtitle2Label.textColor = ActorSDK.sharedActor().style.placeholderHintColor - subtitle2Label.font = UIFont.systemFontOfSize(16.0) - subtitle2Label.textAlignment = NSTextAlignment.Center + subtitle2Label.font = UIFont.systemFont(ofSize: 16.0) + subtitle2Label.textAlignment = NSTextAlignment.center subtitle2Label.numberOfLines = 0 contentView.addSubview(subtitle2Label) - action2Button = UIButton(type: .System) + action2Button = UIButton(type: .system) action2Button.titleLabel!.font = UIFont.mediumSystemFontOfSize(21) contentView.addSubview(action2Button) } @@ -78,73 +78,73 @@ class AABigPlaceholderView: UIView { // Setting image // - func setImage(image: UIImage?, title: String?, subtitle: String?) { + func setImage(_ image: UIImage?, title: String?, subtitle: String?) { setImage(image, title: title, subtitle: subtitle, actionTitle: nil, subtitle2: nil, actionTarget: nil, actionSelector: nil, action2title: nil, action2Selector: nil) } - func setImage(image: UIImage?, title: String?, subtitle: String?, actionTitle: String?, subtitle2: String?, actionTarget: AnyObject?, actionSelector: Selector?, action2title: String?, action2Selector: Selector?) { + func setImage(_ image: UIImage?, title: String?, subtitle: String?, actionTitle: String?, subtitle2: String?, actionTarget: AnyObject?, actionSelector: Selector?, action2title: String?, action2Selector: Selector?) { if image != nil { imageView.image = image! - imageView.hidden = false + imageView.isHidden = false } else { - imageView.hidden = true + imageView.isHidden = true } if title != nil { titleLabel.text = title - titleLabel.hidden = false + titleLabel.isHidden = false } else { - titleLabel.hidden = true + titleLabel.isHidden = true } if subtitle != nil { let paragraphStyle = NSMutableParagraphStyle() paragraphStyle.lineHeightMultiple = 1.11 - paragraphStyle.alignment = NSTextAlignment.Center + paragraphStyle.alignment = NSTextAlignment.center let attrString = NSMutableAttributedString(string: subtitle!) attrString.addAttribute(NSParagraphStyleAttributeName, value:paragraphStyle, range:NSMakeRange(0, attrString.length)) subtitleLabel.attributedText = attrString - subtitleLabel.hidden = false + subtitleLabel.isHidden = false } else { - subtitleLabel.hidden = true + subtitleLabel.isHidden = true } if actionTitle != nil && actionTarget != nil && actionSelector != nil { - actionButton.removeTarget(nil, action: nil, forControlEvents: UIControlEvents.AllEvents) - actionButton.addTarget(actionTarget!, action: actionSelector!, forControlEvents: UIControlEvents.TouchUpInside) - actionButton.setTitle(actionTitle, forState: UIControlState.Normal) - actionButton.hidden = false + actionButton.removeTarget(nil, action: nil, for: UIControlEvents.allEvents) + actionButton.addTarget(actionTarget!, action: actionSelector!, for: UIControlEvents.touchUpInside) + actionButton.setTitle(actionTitle, for: UIControlState()) + actionButton.isHidden = false } else { - actionButton.hidden = true + actionButton.isHidden = true } if (subtitle2 != nil) { let paragraphStyle = NSMutableParagraphStyle() paragraphStyle.lineHeightMultiple = 1.11 - paragraphStyle.alignment = NSTextAlignment.Center + paragraphStyle.alignment = NSTextAlignment.center let attrString = NSMutableAttributedString(string: subtitle2!) attrString.addAttribute(NSParagraphStyleAttributeName, value:paragraphStyle, range:NSMakeRange(0, attrString.length)) subtitle2Label.attributedText = attrString - subtitle2Label.hidden = false + subtitle2Label.isHidden = false } else { - subtitle2Label.hidden = true + subtitle2Label.isHidden = true } if action2title != nil && actionTarget != nil && actionSelector != nil { - action2Button.removeTarget(nil, action: nil, forControlEvents: UIControlEvents.AllEvents) - action2Button.addTarget(actionTarget!, action: action2Selector!, forControlEvents: UIControlEvents.TouchUpInside) - action2Button.setTitle(action2title, forState: UIControlState.Normal) - action2Button.hidden = false + action2Button.removeTarget(nil, action: nil, for: UIControlEvents.allEvents) + action2Button.addTarget(actionTarget!, action: action2Selector!, for: UIControlEvents.touchUpInside) + action2Button.setTitle(action2title, for: UIControlState()) + action2Button.isHidden = false } else { - action2Button.hidden = true + action2Button.isHidden = true } setNeedsLayout() @@ -160,7 +160,7 @@ class AABigPlaceholderView: UIView { var contentHeight: CGFloat = 0 let maxContentWidth = bounds.size.width - 40 - if imageView.hidden == false { + if imageView.isHidden == false { imageView.frame = CGRect(x: 20 + (maxContentWidth - imageView.image!.size.width) / 2.0, y: topOffset, width: imageView.image!.size.width, height: imageView.image!.size.height) contentHeight += imageView.image!.size.height + topOffset @@ -168,7 +168,7 @@ class AABigPlaceholderView: UIView { bgView.frame = CGRect(x: 0, y: 0, width: bounds.size.width, height: imageView.frame.height * 0.75 + topOffset) - if titleLabel.hidden == false { + if titleLabel.isHidden == false { if contentHeight > 0 { contentHeight += 10 } @@ -177,42 +177,42 @@ class AABigPlaceholderView: UIView { contentHeight += titleLabel.bounds.size.height } - if subtitleLabel.hidden == false { + if subtitleLabel.isHidden == false { if contentHeight > 0 { contentHeight += 14 } - let subtitleLabelSize = subtitleLabel.sizeThatFits(CGSize(width: maxContentWidth, height: CGFloat.max)) + let subtitleLabelSize = subtitleLabel.sizeThatFits(CGSize(width: maxContentWidth, height: CGFloat.greatestFiniteMagnitude)) subtitleLabel.frame = CGRect(x: 20, y: contentHeight, width: maxContentWidth, height: subtitleLabelSize.height) contentHeight += subtitleLabelSize.height } - if actionButton.hidden == false { + if actionButton.isHidden == false { if contentHeight > 0 { contentHeight += 14 } - let actionButtonTitleLabelSize = actionButton.titleLabel!.sizeThatFits(CGSize(width: maxContentWidth, height: CGFloat.max)) + let actionButtonTitleLabelSize = actionButton.titleLabel!.sizeThatFits(CGSize(width: maxContentWidth, height: CGFloat.greatestFiniteMagnitude)) actionButton.frame = CGRect(x: 20, y: contentHeight, width: maxContentWidth, height: actionButtonTitleLabelSize.height) contentHeight += actionButtonTitleLabelSize.height } - if subtitle2Label.hidden == false { + if subtitle2Label.isHidden == false { if contentHeight > 0 { contentHeight += 14 } - let subtitleLabelSize = subtitle2Label.sizeThatFits(CGSize(width: maxContentWidth, height: CGFloat.max)) + let subtitleLabelSize = subtitle2Label.sizeThatFits(CGSize(width: maxContentWidth, height: CGFloat.greatestFiniteMagnitude)) subtitle2Label.frame = CGRect(x: 20, y: contentHeight, width: maxContentWidth, height: subtitleLabelSize.height) contentHeight += subtitleLabelSize.height } - if action2Button.hidden == false { + if action2Button.isHidden == false { if contentHeight > 0 { contentHeight += 14 } - let actionButtonTitleLabelSize = action2Button.titleLabel!.sizeThatFits(CGSize(width: maxContentWidth, height: CGFloat.max)) + let actionButtonTitleLabelSize = action2Button.titleLabel!.sizeThatFits(CGSize(width: maxContentWidth, height: CGFloat.greatestFiniteMagnitude)) action2Button.frame = CGRect(x: 20, y: contentHeight, width: maxContentWidth, height: actionButtonTitleLabelSize.height) contentHeight += actionButtonTitleLabelSize.height } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/AACircleButton.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/AACircleButton.swift index e0bd4c258e..7456a2a582 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/AACircleButton.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/AACircleButton.swift @@ -4,21 +4,21 @@ import Foundation -public class AACircleButton: UIView { +open class AACircleButton: UIView { - private let buttonSize: CGFloat + fileprivate let buttonSize: CGFloat - public let button = UIButton() - private let titleView = UILabel() - private let borderView = UIImageView() + open let button = UIButton() + fileprivate let titleView = UILabel() + fileprivate let borderView = UIImageView() - public var image: UIImage? { + open var image: UIImage? { didSet(v) { updateStyles() } } - public var title: String? { + open var title: String? { get { return titleView.text } @@ -27,21 +27,21 @@ public class AACircleButton: UIView { } } - public var filled: Bool = false { + open var filled: Bool = false { didSet(v) { updateStyles() } } - public var enabled: Bool = true { + open var enabled: Bool = true { didSet(v) { - button.enabled = v - button.userInteractionEnabled = v + button.isEnabled = v + button.isUserInteractionEnabled = v updateStyles() } } - public var borderColor: UIColor = UIColor.whiteColor().alpha(0.87) { + open var borderColor: UIColor = UIColor.white.alpha(0.87) { didSet(v) { updateStyles() } @@ -49,17 +49,17 @@ public class AACircleButton: UIView { public init(size: CGFloat) { self.buttonSize = size - super.init(frame: CGRectMake(0, 0, size, size)) + super.init(frame: CGRect(x: 0, y: 0, width: size, height: size)) - borderView.bounds = CGRectMake(0, 0, size, size) + borderView.bounds = CGRect(x: 0, y: 0, width: size, height: size) borderView.frame = borderView.bounds titleView.font = UIFont.thinSystemFontOfSize(17) - titleView.textAlignment = .Center - titleView.bounds = CGRectMake(0, 0, 86, 44) + titleView.textAlignment = .center + titleView.bounds = CGRect(x: 0, y: 0, width: 86, height: 44) titleView.adjustsFontSizeToFitWidth = true - button.bounds = CGRectMake(0, 0, size, size) + button.bounds = CGRect(x: 0, y: 0, width: size, height: size) updateStyles() @@ -72,24 +72,24 @@ public class AACircleButton: UIView { fatalError("init(coder:) has not been implemented") } - private func updateStyles() { - let mainColor = enabled ? UIColor.whiteColor() : UIColor.whiteColor().alpha(0.3) - let selectedColor = enabled ? UIColor.blackColor() : UIColor.blackColor().alpha(0.3) + fileprivate func updateStyles() { + let mainColor = enabled ? UIColor.white : UIColor.white.alpha(0.3) + let selectedColor = enabled ? UIColor.black : UIColor.black.alpha(0.3) titleView.textColor = mainColor if (filled) { borderView.image = Imaging.roundedImage(mainColor, radius: buttonSize / 2) - button.setImage(image?.tintImage(selectedColor), forState: .Normal) + button.setImage(image?.tintImage(selectedColor), for: UIControlState()) } else { borderView.image = Imaging.circleImage(mainColor, radius: buttonSize / 2) - button.setImage(image?.tintImage(mainColor), forState: .Normal) + button.setImage(image?.tintImage(mainColor), for: UIControlState()) } } - public override func layoutSubviews() { + open override func layoutSubviews() { super.layoutSubviews() button.topIn(self.bounds) borderView.topIn(self.bounds) titleView.under(button.frame, offset: 5) } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/AAMapFastView.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/AAMapFastView.swift index 8c7c792f5f..df67198b2e 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/AAMapFastView.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/AAMapFastView.swift @@ -5,9 +5,9 @@ import Foundation import MapKit -public class AAMapFastView: UIImageView { +open class AAMapFastView: UIImageView { - static private var mapCache = AASwiftlyLRU(capacity: 16) + static fileprivate var mapCache = AASwiftlyLRU(capacity: 16) let mapWidth: CGFloat let mapHeight: CGFloat @@ -23,7 +23,7 @@ public class AAMapFastView: UIImageView { fatalError("init(coder:) has not been implemented") } - func bind(latitude: Double, longitude: Double) { + func bind(_ latitude: Double, longitude: Double) { let key = "\(Int(latitude * 1000000))_\(Int(longitude * 1000000))" // Same Key @@ -41,11 +41,11 @@ public class AAMapFastView: UIImageView { let options = MKMapSnapshotOptions() options.region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: latitude, longitude: longitude), span: MKCoordinateSpan(latitudeDelta: 0.005, longitudeDelta: 0.005)) - options.size = CGSizeMake(mapWidth, mapHeight) - options.scale = UIScreen.mainScreen().scale + options.size = CGSize(width: mapWidth, height: mapHeight) + options.scale = UIScreen.main.scale let snapshotter = MKMapSnapshotter(options: options) - snapshotter.startWithCompletionHandler { snapshot, error in + snapshotter.start (completionHandler: { snapshot, error in if let img = snapshot?.image { let rounded = img.roundCorners(img.size.width, h: img.size.height, roundSize: 14) dispatchOnUi { @@ -53,6 +53,6 @@ public class AAMapFastView: UIImageView { self.image = rounded } } - } + }) } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/AAMapPinPointView.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/AAMapPinPointView.swift index b8aa1488c2..d38d10d673 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/AAMapPinPointView.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/AAMapPinPointView.swift @@ -4,25 +4,25 @@ import Foundation -public class AAMapPinPointView: UIView { +open class AAMapPinPointView: UIView { let pinView = UIImageView() let pinPointView = UIImageView() let pinShadowView = UIImageView() public init() { - super.init(frame: CGRectMake(0, 0, 100, 100)) + super.init(frame: CGRect(x: 0, y: 0, width: 100, height: 100)) - pinShadowView.frame = CGRectMake(43, 47, 32, 39) + pinShadowView.frame = CGRect(x: 43, y: 47, width: 32, height: 39) pinShadowView.alpha = 0.9 pinShadowView.image = UIImage.bundled("LocationPinShadow.png") addSubview(pinShadowView) - pinPointView.frame = CGRectMake(100 / 2 - 2, 100 - 18.5, 3.5, 1.5) + pinPointView.frame = CGRect(x: 100 / 2 - 2, y: 100 - 18.5, width: 3.5, height: 1.5) pinPointView.image = UIImage.bundled("LocationPinPoint.png") addSubview(pinPointView) - pinView.frame = CGRectMake(100 / 2 - 7, 47, 13.5, 36) + pinView.frame = CGRect(x: 100 / 2 - 7, y: 47, width: 13.5, height: 36) pinView.image = UIImage.bundled("LocationPin.png") addSubview(pinView) } @@ -31,45 +31,45 @@ public class AAMapPinPointView: UIView { fatalError("init(coder:) has not been implemented") } - func risePin(rised: Bool, animated: Bool) { + func risePin(_ rised: Bool, animated: Bool) { self.pinShadowView.layer.removeAllAnimations() self.pinView.layer.removeAllAnimations() if animated { if rised { - UIView.animateWithDuration(0.2, delay: 0.0, options: .BeginFromCurrentState, animations: { () -> Void in - self.pinView.frame = CGRectMake(100 / 2 - 7, 7, 13.5, 36) - self.pinShadowView.frame = CGRectMake(87, -33, 32, 39) + UIView.animate(withDuration: 0.2, delay: 0.0, options: .beginFromCurrentState, animations: { () -> Void in + self.pinView.frame = CGRect(x: 100 / 2 - 7, y: 7, width: 13.5, height: 36) + self.pinShadowView.frame = CGRect(x: 87, y: -33, width: 32, height: 39) }, completion: nil) } else { - UIView.animateWithDuration(0.2, delay: 0.0, options: .BeginFromCurrentState, animations: { () -> Void in - self.pinView.frame = CGRectMake(100 / 2 - 7, 47, 13.5, 36) - self.pinShadowView.frame = CGRectMake(43, 47, 32, 39) + UIView.animate(withDuration: 0.2, delay: 0.0, options: .beginFromCurrentState, animations: { () -> Void in + self.pinView.frame = CGRect(x: 100 / 2 - 7, y: 47, width: 13.5, height: 36) + self.pinShadowView.frame = CGRect(x: 43, y: 47, width: 32, height: 39) }, completion: { finished in if !finished { return } - UIView.animateWithDuration(0.1, delay: 0.0, options: .BeginFromCurrentState, animations: { () -> Void in - self.pinView.frame = CGRectMake(100 / 2 - 7, 47 + 2, 13.5, 36 - 2) + UIView.animate(withDuration: 0.1, delay: 0.0, options: .beginFromCurrentState, animations: { () -> Void in + self.pinView.frame = CGRect(x: 100 / 2 - 7, y: 47 + 2, width: 13.5, height: 36 - 2) }, completion: { (finished) -> Void in if !finished { return } - UIView.animateWithDuration(0.1, delay: 0.0, options: .BeginFromCurrentState, animations: { () -> Void in - self.pinView.frame = CGRectMake(100 / 2 - 7, 47, 13.5, 36) + UIView.animate(withDuration: 0.1, delay: 0.0, options: .beginFromCurrentState, animations: { () -> Void in + self.pinView.frame = CGRect(x: 100 / 2 - 7, y: 47, width: 13.5, height: 36) }, completion: nil) }) }) } } else { if rised { - self.pinView.frame = CGRectMake(100 / 2 - 7, 7, 13.5, 36) - self.pinShadowView.frame = CGRectMake(87, -33, 32, 39) + self.pinView.frame = CGRect(x: 100 / 2 - 7, y: 7, width: 13.5, height: 36) + self.pinShadowView.frame = CGRect(x: 87, y: -33, width: 32, height: 39) } else { - self.pinView.frame = CGRectMake(100 / 2 - 7, 47, 13.5, 36) - self.pinShadowView.frame = CGRectMake(43, 47, 32, 39) + self.pinView.frame = CGRect(x: 100 / 2 - 7, y: 47, width: 13.5, height: 36) + self.pinShadowView.frame = CGRect(x: 43, y: 47, width: 32, height: 39) } } } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/AAProgressView.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/AAProgressView.swift index 07778337ef..0c9f93a32a 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/AAProgressView.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/AAProgressView.swift @@ -5,32 +5,32 @@ import Foundation import VBFPopFlatButton -public class AAProgressView: UIView { +open class AAProgressView: UIView { - private let circlePathLayer = CAShapeLayer() - private let backgroundPathLayer = CAShapeLayer() - private var progressButton: VBFPopFlatButton! + fileprivate let circlePathLayer = CAShapeLayer() + fileprivate let backgroundPathLayer = CAShapeLayer() + fileprivate var progressButton: VBFPopFlatButton! public init(size: CGSize) { - super.init(frame: CGRectMake(0, 0, size.width, size.height)) + super.init(frame: CGRect(x: 0, y: 0, width: size.width, height: size.height)) - self.backgroundColor = UIColor.clearColor() - self.userInteractionEnabled = false + self.backgroundColor = UIColor.clear + self.isUserInteractionEnabled = false - let bgPath = UIBezierPath(ovalInRect: CGRectMake(0, 0, self.bounds.width, self.bounds.height)) + let bgPath = UIBezierPath(ovalIn: CGRect(x: 0, y: 0, width: self.bounds.width, height: self.bounds.height)) backgroundPathLayer.frame = bounds - backgroundPathLayer.path = bgPath.CGPath - backgroundPathLayer.fillColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0x64/255.0).CGColor - backgroundPathLayer.strokeColor = UIColor.clearColor().CGColor + backgroundPathLayer.path = bgPath.cgPath + backgroundPathLayer.fillColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0x64/255.0).cgColor + backgroundPathLayer.strokeColor = UIColor.clear.cgColor layer.addSublayer(backgroundPathLayer) - let circlePath = UIBezierPath(ovalInRect: CGRectMake(3, 3, self.bounds.width - 6, self.bounds.height - 6)) + let circlePath = UIBezierPath(ovalIn: CGRect(x: 3, y: 3, width: self.bounds.width - 6, height: self.bounds.height - 6)) circlePathLayer.frame = bounds - circlePathLayer.path = circlePath.CGPath + circlePathLayer.path = circlePath.cgPath circlePathLayer.lineWidth = 3 circlePathLayer.lineCap = kCALineCapRound - circlePathLayer.fillColor = UIColor.clearColor().CGColor - circlePathLayer.strokeColor = UIColor.whiteColor().CGColor + circlePathLayer.fillColor = UIColor.clear.cgColor + circlePathLayer.strokeColor = UIColor.white.cgColor circlePathLayer.strokeStart = 0 circlePathLayer.strokeEnd = 0.5 layer.addSublayer(circlePathLayer) @@ -56,33 +56,33 @@ public class AAProgressView: UIView { fatalError("init(coder:) has not been implemented") } - public func setButtonType(type: FlatButtonType, animated: Bool) { + open func setButtonType(_ type: FlatButtonType, animated: Bool) { if progressButton != nil && animated { - progressButton.animateToType(type) + progressButton.animate(to: type) } else { hideButton() let size: CGFloat = self.bounds.width < 64 ? 24 : 32 let x = (self.bounds.width - size) / 2 let y = (self.bounds.height - size) / 2 progressButton = VBFPopFlatButton(frame: CGRect(x: x, y: y, width: size, height: size), buttonType: type, buttonStyle: FlatButtonStyle.buttonPlainStyle, animateToInitialState: animated) - progressButton.userInteractionEnabled = false + progressButton.isUserInteractionEnabled = false self.addSubview(progressButton) } } - public func setProgress(value: Double) { + open func setProgress(_ value: Double) { circlePathLayer.strokeEnd = CGFloat(value) } - public func hideProgress() { + open func hideProgress() { circlePathLayer.strokeEnd = 0 } - public func hideButton() { + open func hideButton() { if progressButton != nil { progressButton.removeFromSuperview() progressButton = nil } } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/AAStickerView.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/AAStickerView.swift index 63e1bd8a8c..030c2919ec 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/AAStickerView.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/AAStickerView.swift @@ -5,16 +5,16 @@ import Foundation import YYImage -public class AAStickerView: UIView, YYAsyncLayerDelegate, ACFileEventCallback { +open class AAStickerView: UIView, YYAsyncLayerDelegate, ACFileEventCallback { - private var file: ACFileReference? + fileprivate var file: ACFileReference? public init() { - super.init(frame: CGRectZero) + super.init(frame: CGRect.zero) layer.delegate = self - layer.contentsScale = UIScreen.mainScreen().scale - backgroundColor = UIColor.clearColor() - Actor.subscribeToDownloads(self) + layer.contentsScale = UIScreen.main.scale + backgroundColor = UIColor.clear + Actor.subscribe(toDownloads: self) } public required init?(coder aDecoder: NSCoder) { @@ -22,10 +22,10 @@ public class AAStickerView: UIView, YYAsyncLayerDelegate, ACFileEventCallback { } deinit { - Actor.unsubscribeFromDownloads(self) + Actor.unsubscribe(fromDownloads: self) } - public func onDownloadedWithLong(fileId: jlong) { + open func onDownloaded(withLong fileId: jlong) { if self.file?.getFileId() == fileId { dispatchOnUi { if self.file?.getFileId() == fileId { @@ -35,16 +35,16 @@ public class AAStickerView: UIView, YYAsyncLayerDelegate, ACFileEventCallback { } } - public func setSticker(file: ACFileReference?) { + open func setSticker(_ file: ACFileReference?) { self.file = file self.layer.setNeedsDisplay() } - public override class func layerClass() -> AnyClass { + open override class var layerClass : AnyClass { return YYAsyncLayer.self } - public func newAsyncDisplayTask() -> YYAsyncLayerDisplayTask { + open func newAsyncDisplayTask() -> YYAsyncLayerDisplayTask { let res = YYAsyncLayerDisplayTask() @@ -52,7 +52,7 @@ public class AAStickerView: UIView, YYAsyncLayerDelegate, ACFileEventCallback { res.display = { (context: CGContext, size: CGSize, isCancelled: () -> Bool) -> () in if _file != nil { - let desc = Actor.findDownloadedDescriptorWithFileId(_file!.getFileId()) + let desc = Actor.findDownloadedDescriptor(withFileId: _file!.getFileId()) if isCancelled() { return } @@ -62,14 +62,14 @@ public class AAStickerView: UIView, YYAsyncLayerDelegate, ACFileEventCallback { if isCancelled() { return } - image?.drawInRect(CGRectMake(0, 0, size.width, size.height), withContentMode: UIViewContentMode.ScaleAspectFit, clipsToBounds: true) + image?.draw(in: CGRect(x: 0, y: 0, width: size.width, height: size.height), with: UIViewContentMode.scaleAspectFit, clipsToBounds: true) } else { // Request if not available - Actor.startDownloadingWithReference(_file!) + Actor.startDownloading(with: _file!) } } } return res } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/AATableViewHeader.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/AATableViewHeader.swift index 70481487c3..62ef048b65 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/AATableViewHeader.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/AATableViewHeader.swift @@ -4,21 +4,21 @@ import Foundation -public class AATableViewHeader: UIView { +open class AATableViewHeader: UIView { - public override func layoutSubviews() { + open override func layoutSubviews() { super.layoutSubviews() for view in self.subviews { - view.frame = CGRectMake(view.frame.minX, view.frame.minY, self.frame.width, view.frame.height) + view.frame = CGRect(x: view.frame.minX, y: view.frame.minY, width: self.frame.width, height: view.frame.height) // Fix for UISearchBar disappear // http://stackoverflow.com/questions/19044156/searchbar-disappears-from-headerview-in-ios-7 if let search = view as? UISearchBar { if let buggyView = search.subviews.first { buggyView.bounds = search.bounds - buggyView.center = CGPointMake(buggyView.bounds.width/2,buggyView.bounds.height/2) + buggyView.center = CGPoint(x: buggyView.bounds.width/2,y: buggyView.bounds.height/2) } } } } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/AATableViewSeparator.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/AATableViewSeparator.swift index fc450b640e..bf0eb532ea 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/AATableViewSeparator.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/AATableViewSeparator.swift @@ -8,7 +8,7 @@ import UIKit class AATableViewSeparator : UIView { init(color: UIColor) { - super.init(frame: CGRectZero) + super.init(frame: CGRect.zero) super.backgroundColor = color } @@ -25,4 +25,4 @@ import UIKit } } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/Cells/AAAvatarCell.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/Cells/AAAvatarCell.swift index 002b9e7f5e..6e4d2be5e7 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/Cells/AAAvatarCell.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/Cells/AAAvatarCell.swift @@ -4,35 +4,35 @@ import Foundation -public class AAAvatarCell: AATableViewCell { +open class AAAvatarCell: AATableViewCell { - public var titleLabel = UILabel() - public var subtitleLabel = UILabel() - public var avatarView = AAAvatarView() - public var progress = UIActivityIndicatorView(activityIndicatorStyle: .White) - public var didTap: ((view: UIView)->())? + open var titleLabel = UILabel() + open var subtitleLabel = UILabel() + open var avatarView = AAAvatarView() + open var progress = UIActivityIndicatorView(activityIndicatorStyle: .white) + open var didTap: ((_ view: UIView)->())? public override init(style: UITableViewCellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) avatarView = AAAvatarView() avatarView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(AAAvatarCell.avatarDidTap))) - avatarView.userInteractionEnabled = true + avatarView.isUserInteractionEnabled = true contentView.addSubview(avatarView) - titleLabel.backgroundColor = UIColor.clearColor() + titleLabel.backgroundColor = UIColor.clear titleLabel.textColor = ActorSDK.sharedActor().style.cellTextColor - titleLabel.font = UIFont.systemFontOfSize(20.0) + titleLabel.font = UIFont.systemFont(ofSize: 20.0) contentView.addSubview(titleLabel) - subtitleLabel.backgroundColor = UIColor.clearColor() + subtitleLabel.backgroundColor = UIColor.clear subtitleLabel.textColor = ActorSDK.sharedActor().style.cellHintColor - subtitleLabel.font = UIFont.systemFontOfSize(14.0) + subtitleLabel.font = UIFont.systemFont(ofSize: 14.0) contentView.addSubview(subtitleLabel) contentView.addSubview(progress) - selectionStyle = .None + selectionStyle = .none } public required init(coder aDecoder: NSCoder) { @@ -40,20 +40,20 @@ public class AAAvatarCell: AATableViewCell { } func avatarDidTap() { - didTap?(view: avatarView) + didTap?(avatarView) } - public override func layoutSubviews() { + open override func layoutSubviews() { super.layoutSubviews() avatarView.frame = CGRect(x: 14, y: 14, width: 66, height: 66) progress.frame = avatarView.frame - if subtitleLabel.hidden { + if subtitleLabel.isHidden { titleLabel.frame = CGRect(x: 82 + 6, y: 14 + 64/2 - 14, width: self.contentView.bounds.width - 82 - 14 - 10, height: 24) } else { titleLabel.frame = CGRect(x: 82 + 6, y: 14 + 64/2 - 24, width: self.contentView.bounds.width - 82 - 14 - 10, height: 24) subtitleLabel.frame = CGRect(x: 82 + 6, y: 14 + 66/2 + 4, width: self.contentView.bounds.width - 82 - 14 - 10, height: 16) } } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/Cells/AABackgroundCellRenderer.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/Cells/AABackgroundCellRenderer.swift index 5f0f36338d..2349f4e431 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/Cells/AABackgroundCellRenderer.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/Cells/AABackgroundCellRenderer.swift @@ -5,20 +5,20 @@ import Foundation -public class AABackgroundCellRenderer { +open class AABackgroundCellRenderer where T: AnyObject, P: AnyObject, P: Equatable { - private var generation = 0 - private var wasPresented: Bool = false - private var requestedConfig: P? = nil - private let renderer: (config: P)-> T! - private let receiver: (T!) -> () + fileprivate var generation = 0 + fileprivate var wasPresented: Bool = false + fileprivate var requestedConfig: P? = nil + fileprivate let renderer: (_ config: P)-> T! + fileprivate let receiver: (T!) -> () - public init(renderer: (config: P) -> T!, receiver: (T!) -> ()) { + public init(renderer: @escaping (_ config: P) -> T!, receiver: @escaping (T!) -> ()) { self.renderer = renderer self.receiver = receiver } - func requestRender(config: P) -> Bool { + func requestRender(_ config: P) -> Bool { // Ignore if not resized if requestedConfig == config { return false @@ -44,7 +44,7 @@ public class AABackgroundCellRenderer ())? - public var contentInset: CGFloat = 15 + open var style: AACommonCellStyle = .normal { didSet { updateCellStyle() } } + open var switchBlock: ((Bool) -> ())? + open var contentInset: CGFloat = 15 public override init(style: UITableViewCellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) - titleLabel.font = UIFont.systemFontOfSize(17.0) + titleLabel.font = UIFont.systemFont(ofSize: 17.0) contentView.addSubview(titleLabel) - hintLabel.font = UIFont.systemFontOfSize(17.0) + hintLabel.font = UIFont.systemFont(ofSize: 17.0) hintLabel.textColor = appStyle.cellHintColor contentView.addSubview(hintLabel) } @@ -43,100 +43,100 @@ public class AACommonCell: AATableViewCell { // Setting text content - public func setContent(content: String?) { + open func setContent(_ content: String?) { titleLabel.text = content } - public func setHint(hint: String?) { + open func setHint(_ hint: String?) { if hint == nil { - hintLabel.hidden = true + hintLabel.isHidden = true } else { hintLabel.text = hint - hintLabel.hidden = false + hintLabel.isHidden = false } setNeedsLayout() } // Setting switcher content - public func setSwitcherOn(on: Bool) { + open func setSwitcherOn(_ on: Bool) { setSwitcherOn(on, animated: false) } - public func setSwitcherOn(on: Bool, animated: Bool) { + open func setSwitcherOn(_ on: Bool, animated: Bool) { switcher?.setOn(on, animated: animated) } - public func setSwitcherEnabled(enabled: Bool) { - switcher?.enabled = enabled + open func setSwitcherEnabled(_ enabled: Bool) { + switcher?.isEnabled = enabled } // Private methods - private func updateCellStyle() { + fileprivate func updateCellStyle() { switch (style) { - case .Normal: + case .normal: titleLabel.textColor = appStyle.cellTextColor - titleLabel.textAlignment = NSTextAlignment.Left - switcher?.hidden = true - accessoryType = UITableViewCellAccessoryType.None + titleLabel.textAlignment = NSTextAlignment.left + switcher?.isHidden = true + accessoryType = UITableViewCellAccessoryType.none break - case .Hint: + case .hint: titleLabel.textColor = appStyle.cellHintColor - titleLabel.textAlignment = NSTextAlignment.Left - switcher?.hidden = true - accessoryType = UITableViewCellAccessoryType.None + titleLabel.textAlignment = NSTextAlignment.left + switcher?.isHidden = true + accessoryType = UITableViewCellAccessoryType.none break - case .DestructiveCentered: + case .destructiveCentered: titleLabel.textColor = appStyle.cellDestructiveColor - titleLabel.textAlignment = NSTextAlignment.Center - switcher?.hidden = true - accessoryType = UITableViewCellAccessoryType.None + titleLabel.textAlignment = NSTextAlignment.center + switcher?.isHidden = true + accessoryType = UITableViewCellAccessoryType.none break - case .Destructive: + case .destructive: titleLabel.textColor = appStyle.cellDestructiveColor - titleLabel.textAlignment = NSTextAlignment.Left - switcher?.hidden = true - accessoryType = UITableViewCellAccessoryType.None + titleLabel.textAlignment = NSTextAlignment.left + switcher?.isHidden = true + accessoryType = UITableViewCellAccessoryType.none break - case .Switch: + case .switch: titleLabel.textColor = appStyle.cellTextColor - titleLabel.textAlignment = NSTextAlignment.Left + titleLabel.textAlignment = NSTextAlignment.left setupSwitchIfNeeded() - switcher?.hidden = false - accessoryType = UITableViewCellAccessoryType.None + switcher?.isHidden = false + accessoryType = UITableViewCellAccessoryType.none break - case .Action: + case .action: titleLabel.textColor = appStyle.cellTintColor - titleLabel.textAlignment = NSTextAlignment.Left - switcher?.hidden = true - accessoryType = UITableViewCellAccessoryType.None + titleLabel.textAlignment = NSTextAlignment.left + switcher?.isHidden = true + accessoryType = UITableViewCellAccessoryType.none break - case .ActionCentered: + case .actionCentered: titleLabel.textColor = appStyle.cellTintColor - titleLabel.textAlignment = NSTextAlignment.Center - switcher?.hidden = true - accessoryType = UITableViewCellAccessoryType.None + titleLabel.textAlignment = NSTextAlignment.center + switcher?.isHidden = true + accessoryType = UITableViewCellAccessoryType.none break - case .Navigation: + case .navigation: titleLabel.textColor = appStyle.cellTextColor - titleLabel.textAlignment = NSTextAlignment.Left - switcher?.hidden = true - accessoryType = UITableViewCellAccessoryType.DisclosureIndicator + titleLabel.textAlignment = NSTextAlignment.left + switcher?.isHidden = true + accessoryType = UITableViewCellAccessoryType.disclosureIndicator - case .Checkmark: + case .checkmark: titleLabel.textColor = appStyle.cellTextColor - titleLabel.textAlignment = NSTextAlignment.Left - switcher?.hidden = true - accessoryType = UITableViewCellAccessoryType.Checkmark + titleLabel.textAlignment = NSTextAlignment.left + switcher?.isHidden = true + accessoryType = UITableViewCellAccessoryType.checkmark break } } - private func setupSwitchIfNeeded() { + fileprivate func setupSwitchIfNeeded() { if switcher == nil { switcher = UISwitch() - switcher!.addTarget(self, action: #selector(AACommonCell.switcherSwitched), forControlEvents: UIControlEvents.ValueChanged) + switcher!.addTarget(self, action: #selector(AACommonCell.switcherSwitched), for: UIControlEvents.valueChanged) switcher!.onTintColor = appStyle.vcSwitchOn switcher!.tintColor = appStyle.vcSwitchOff contentView.addSubview(switcher!) @@ -145,26 +145,26 @@ public class AACommonCell: AATableViewCell { func switcherSwitched() { if switchBlock != nil { - switchBlock!(switcher!.on) + switchBlock!(switcher!.isOn) } } - public override func layoutSubviews() { + open override func layoutSubviews() { super.layoutSubviews() if hintLabel.text != nil { - hintLabel.frame = CGRectMake(0, 0, 100, 44) + hintLabel.frame = CGRect(x: 0, y: 0, width: 100, height: 44) hintLabel.sizeToFit() - if accessoryType == UITableViewCellAccessoryType.None { - hintLabel.frame = CGRectMake(contentView.bounds.width - hintLabel.width - 15, 0, hintLabel.width, 44) - titleLabel.frame = CGRectMake(contentInset, 0, contentView.bounds.width - hintLabel.width - contentInset - 20, 44) + if accessoryType == UITableViewCellAccessoryType.none { + hintLabel.frame = CGRect(x: contentView.bounds.width - hintLabel.width - 15, y: 0, width: hintLabel.width, height: 44) + titleLabel.frame = CGRect(x: contentInset, y: 0, width: contentView.bounds.width - hintLabel.width - contentInset - 20, height: 44) } else { - hintLabel.frame = CGRectMake(contentView.bounds.width - hintLabel.width, 0, hintLabel.width, 44) - titleLabel.frame = CGRectMake(contentInset, 0, contentView.bounds.width - hintLabel.width - contentInset - 5, 44) + hintLabel.frame = CGRect(x: contentView.bounds.width - hintLabel.width, y: 0, width: hintLabel.width, height: 44) + titleLabel.frame = CGRect(x: contentInset, y: 0, width: contentView.bounds.width - hintLabel.width - contentInset - 5, height: 44) } } else { - titleLabel.frame = CGRectMake(contentInset, 0, contentView.bounds.width - contentInset - 5, 44) + titleLabel.frame = CGRect(x: contentInset, y: 0, width: contentView.bounds.width - contentInset - 5, height: 44) } if switcher != nil { @@ -172,4 +172,4 @@ public class AACommonCell: AATableViewCell { switcher!.frame = CGRect(x: contentView.bounds.width - switcherSize.width - 15, y: (contentView.bounds.height - switcherSize.height) / 2, width: switcherSize.width, height: switcherSize.height) } } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/Cells/AAEditCell.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/Cells/AAEditCell.swift index cf9f5a06a9..158d002f76 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/Cells/AAEditCell.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/Cells/AAEditCell.swift @@ -4,20 +4,20 @@ import Foundation -public class AAEditCell: AATableViewCell { +open class AAEditCell: AATableViewCell { - public let textPrefix = UILabel() - public let textField = UITextField() + open let textPrefix = UILabel() + open let textField = UITextField() public override init(style: UITableViewCellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) - textField.autocapitalizationType = .None - textField.autocorrectionType = .No + textField.autocapitalizationType = .none + textField.autocorrectionType = .no textField.textColor = appStyle.cellTextColor - textField.keyboardAppearance = appStyle.isDarkApp ? .Dark : .Light + textField.keyboardAppearance = appStyle.isDarkApp ? .dark : .light - textPrefix.hidden = true + textPrefix.isHidden = true contentView.addSubview(textPrefix) contentView.addSubview(textField) @@ -27,16 +27,16 @@ public class AAEditCell: AATableViewCell { fatalError("init(coder:) has not been implemented") } - public override func layoutSubviews() { + open override func layoutSubviews() { super.layoutSubviews() - if textPrefix.hidden { - textField.frame = CGRectMake(15, 0, contentView.width - 30, 44) + if textPrefix.isHidden { + textField.frame = CGRect(x: 15, y: 0, width: contentView.width - 30, height: 44) } else { - textPrefix.frame = CGRectMake(15, 0, contentView.width - 30, 44) + textPrefix.frame = CGRect(x: 15, y: 0, width: contentView.width - 30, height: 44) textPrefix.sizeToFit() - textPrefix.frame = CGRectMake(15, 0, textPrefix.width, 44) - textField.frame = CGRectMake(15 + textPrefix.width, 0, contentView.width - textPrefix.width - 30, 44) + textPrefix.frame = CGRect(x: 15, y: 0, width: textPrefix.width, height: 44) + textField.frame = CGRect(x: 15 + textPrefix.width, y: 0, width: contentView.width - textPrefix.width - 30, height: 44) } } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/Cells/AAHeaderCell.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/Cells/AAHeaderCell.swift index 382a8cf552..f2420cc958 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/Cells/AAHeaderCell.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/Cells/AAHeaderCell.swift @@ -4,22 +4,22 @@ import Foundation -public class AAHeaderCell: AATableViewCell { +open class AAHeaderCell: AATableViewCell { - public var titleView = UILabel() - public var iconView = UIImageView() + open var titleView = UILabel() + open var iconView = UIImageView() public override init(style: UITableViewCellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) contentView.backgroundColor = appStyle.vcBackyardColor - selectionStyle = UITableViewCellSelectionStyle.None + selectionStyle = UITableViewCellSelectionStyle.none titleView.textColor = appStyle.cellHeaderColor - titleView.font = UIFont.systemFontOfSize(14) + titleView.font = UIFont.systemFont(ofSize: 14) contentView.addSubview(titleView) - iconView.contentMode = UIViewContentMode.ScaleAspectFill + iconView.contentMode = UIViewContentMode.scaleAspectFill contentView.addSubview(iconView) } @@ -27,13 +27,13 @@ public class AAHeaderCell: AATableViewCell { fatalError("init(coder:) has not been implemented") } - public override func layoutSubviews() { + open override func layoutSubviews() { super.layoutSubviews() let height = self.contentView.bounds.height let width = self.contentView.bounds.width - titleView.frame = CGRectMake(15, height - 28, width - 48, 24) - iconView.frame = CGRectMake(width - 18 - 15, height - 18 - 4, 18, 18) + titleView.frame = CGRect(x: 15, y: height - 28, width: width - 48, height: 24) + iconView.frame = CGRect(x: width - 18 - 15, y: height - 18 - 4, width: 18, height: 18) } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/Cells/AATableViewCell.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/Cells/AATableViewCell.swift index ed9ae747a3..8bbd68bbde 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/Cells/AATableViewCell.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/Cells/AATableViewCell.swift @@ -4,12 +4,12 @@ import Foundation -public class AATableViewCell: UITableViewCell { +open class AATableViewCell: UITableViewCell { - private static let separatorColor = ActorSDK.sharedActor().style.vcSeparatorColor + fileprivate static let separatorColor = ActorSDK.sharedActor().style.vcSeparatorColor - private var topSeparator: UIView = UIView() - private var bottomSeparator: UIView = UIView() + fileprivate var topSeparator: UIView = UIView() + fileprivate var bottomSeparator: UIView = UIView() var appStyle: ActorStyle { get { @@ -35,18 +35,18 @@ public class AATableViewCell: UITableViewCell { } - public var topSeparatorLeftInset: CGFloat = 0.0 { + open var topSeparatorLeftInset: CGFloat = 0.0 { didSet { setNeedsLayout() } } - public var bottomSeparatorLeftInset: CGFloat = 0.0 { + open var bottomSeparatorLeftInset: CGFloat = 0.0 { didSet { setNeedsLayout() } } - public var topSeparatorVisible: Bool = false { + open var topSeparatorVisible: Bool = false { didSet { if topSeparatorVisible == oldValue { return @@ -54,7 +54,7 @@ public class AATableViewCell: UITableViewCell { if topSeparatorVisible { contentView.addSubview(topSeparator) - contentView.bringSubviewToFront(topSeparator) + contentView.bringSubview(toFront: topSeparator) } else { topSeparator.removeFromSuperview() } @@ -63,7 +63,7 @@ public class AATableViewCell: UITableViewCell { } } - public var bottomSeparatorVisible: Bool = false { + open var bottomSeparatorVisible: Bool = false { didSet { if bottomSeparatorVisible == oldValue { return @@ -80,22 +80,22 @@ public class AATableViewCell: UITableViewCell { } } - public override func layoutSubviews() { + open override func layoutSubviews() { super.layoutSubviews() if topSeparatorVisible { topSeparator.frame = CGRect(x: topSeparatorLeftInset, y: 0, width: bounds.width - topSeparatorLeftInset, height: 0.5) - contentView.bringSubviewToFront(topSeparator) + contentView.bringSubview(toFront: topSeparator) } if bottomSeparatorVisible { bottomSeparator.frame = CGRect(x: bottomSeparatorLeftInset, y: contentView.bounds.height - 0.5, width: bounds.width - bottomSeparatorLeftInset, height: 0.5) - contentView.bringSubviewToFront(bottomSeparator) + contentView.bringSubview(toFront: bottomSeparator) } } - public override func setHighlighted(highlighted: Bool, animated: Bool) { - if self.highlighted != highlighted { + open override func setHighlighted(_ highlighted: Bool, animated: Bool) { + if self.isHighlighted != highlighted { super.setHighlighted(highlighted, animated: animated) } @@ -109,8 +109,8 @@ public class AATableViewCell: UITableViewCell { } } - public override func setSelected(selected: Bool, animated: Bool) { - if self.selected != selected { + open override func setSelected(_ selected: Bool, animated: Bool) { + if self.isSelected != selected { super.setSelected(selected, animated: animated) } @@ -123,4 +123,4 @@ public class AATableViewCell: UITableViewCell { } } } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/Cells/AATextCell.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/Cells/AATextCell.swift index 2f01e2246e..0af8a3c42f 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/Cells/AATextCell.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/Cells/AATextCell.swift @@ -4,24 +4,24 @@ import Foundation -public class AATextCell: AATableViewCell { +open class AATextCell: AATableViewCell { - public var titleLabel: UILabel = UILabel() - public var contentLabel: UILabel = UILabel() + open var titleLabel: UILabel = UILabel() + open var contentLabel: UILabel = UILabel() public override init(style: UITableViewCellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) - titleLabel.font = UIFont.systemFontOfSize(14.0) + titleLabel.font = UIFont.systemFont(ofSize: 14.0) titleLabel.text = " " titleLabel.sizeToFit() titleLabel.textColor = appStyle.cellTintColor contentView.addSubview(titleLabel) - contentLabel.font = UIFont.systemFontOfSize(17.0) + contentLabel.font = UIFont.systemFont(ofSize: 17.0) contentLabel.text = " " contentLabel.textColor = appStyle.cellTextColor - contentLabel.lineBreakMode = NSLineBreakMode.ByWordWrapping + contentLabel.lineBreakMode = NSLineBreakMode.byWordWrapping contentLabel.numberOfLines = 0 contentLabel.sizeToFit() contentView.addSubview(contentLabel) @@ -31,9 +31,9 @@ public class AATextCell: AATableViewCell { fatalError("init(coder:) has not been implemented") } - public func setContent(title: String?, content: String?, isAction: Bool) { + open func setContent(_ title: String?, content: String?, isAction: Bool) { titleLabel.text = title - titleLabel.hidden = title == nil + titleLabel.isHidden = title == nil contentLabel.text = content if isAction { @@ -43,10 +43,10 @@ public class AATextCell: AATableViewCell { } } - public override func layoutSubviews() { + open override func layoutSubviews() { super.layoutSubviews() - if titleLabel.hidden { + if titleLabel.isHidden { contentLabel.frame = CGRect(x: 15, y: 8, width: contentView.bounds.width - 30, height: contentView.height - 16) } else { titleLabel.frame = CGRect(x: 15, y: 7, width: contentView.bounds.width - 30, height: titleLabel.bounds.height) @@ -55,7 +55,7 @@ public class AATextCell: AATableViewCell { } } - public class func measure(title: String?, text: String, width: CGFloat, enableNavigation: Bool) -> CGFloat { + open class func measure(_ title: String?, text: String, width: CGFloat, enableNavigation: Bool) -> CGFloat { let size = UIViewMeasure.measureText(text, width: width - 30 - (enableNavigation ? 30 : 0), fontSize: 17) if title != nil { @@ -64,4 +64,4 @@ public class AATextCell: AATableViewCell { return CGFloat(max(size.height + 16, 44)) } } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/Cells/AATitledCell.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/Cells/AATitledCell.swift index 4c4838101c..e9f4212da0 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/Cells/AATitledCell.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/Cells/AATitledCell.swift @@ -4,11 +4,11 @@ import UIKit -public class AATitledCell: AATableViewCell { +open class AATitledCell: AATableViewCell { - private var isAction: Bool = false - public let titleLabel: UILabel = UILabel() - public let contentLabel: UILabel = UILabel() + fileprivate var isAction: Bool = false + open let titleLabel: UILabel = UILabel() + open let contentLabel: UILabel = UILabel() public override init(style: UITableViewCellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) @@ -22,17 +22,17 @@ public class AATitledCell: AATableViewCell { fatalError("init(coder:) has not been implemented") } - public func setContent(title: String, content: String, isAction: Bool) { + open func setContent(_ title: String, content: String, isAction: Bool) { titleLabel.text = title contentLabel.text = content if isAction { - contentLabel.textColor = UIColor.lightGrayColor() + contentLabel.textColor = UIColor.lightGray } else { contentLabel.textColor = appStyle.cellTextColor } } - public override func layoutSubviews() { + open override func layoutSubviews() { super.layoutSubviews() titleLabel.frame = CGRect(x: separatorInset.left, y: 7, width: contentView.bounds.width - separatorInset.left - 10, height: 19) diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/ViewExtensions.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/ViewExtensions.swift index 2c9028e556..84a0c79463 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/ViewExtensions.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/ViewExtensions.swift @@ -15,7 +15,7 @@ public extension UITabBarItem { self.init(title: AALocalized(title), image: UIImage.tinted(img, color: unselectedIcon), selectedImage: UIImage.tinted(selImage, color: selectedIcon)) - setTitleTextAttributes([NSForegroundColorAttributeName: unselectedText], forState: UIControlState.Normal) - setTitleTextAttributes([NSForegroundColorAttributeName: selectedText], forState: UIControlState.Selected) + setTitleTextAttributes([NSForegroundColorAttributeName: unselectedText], for: UIControlState()) + setTitleTextAttributes([NSForegroundColorAttributeName: selectedText], for: UIControlState.selected) } -} \ No newline at end of file +} diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/WebRTCExt.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/WebRTCExt.swift index 536238f1c3..b70c9c19c4 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/WebRTCExt.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/WebRTCExt.swift @@ -9,54 +9,54 @@ class AAPeerConnectionDelegate: NSObject, RTCPeerConnectionDelegate { var onCandidateReceived: ((RTCICECandidate)->())? var onStreamAdded: ((RTCMediaStream) -> ())? - func peerConnection(peerConnection: RTCPeerConnection!, signalingStateChanged stateChanged: RTCSignalingState) { + func peerConnection(_ peerConnection: RTCPeerConnection!, signalingStateChanged stateChanged: RTCSignalingState) { } - func peerConnection(peerConnection: RTCPeerConnection!, addedStream stream: RTCMediaStream!) { + func peerConnection(_ peerConnection: RTCPeerConnection!, addedStream stream: RTCMediaStream!) { onStreamAdded?(stream) } - func peerConnection(peerConnection: RTCPeerConnection!, removedStream stream: RTCMediaStream!) { + func peerConnection(_ peerConnection: RTCPeerConnection!, removedStream stream: RTCMediaStream!) { } - func peerConnectionOnRenegotiationNeeded(peerConnection: RTCPeerConnection!) { + func peerConnection(onRenegotiationNeeded peerConnection: RTCPeerConnection!) { } - func peerConnection(peerConnection: RTCPeerConnection!, iceConnectionChanged newState: RTCICEConnectionState) { + func peerConnection(_ peerConnection: RTCPeerConnection!, iceConnectionChanged newState: RTCICEConnectionState) { } - func peerConnection(peerConnection: RTCPeerConnection!, iceGatheringChanged newState: RTCICEGatheringState) { + func peerConnection(_ peerConnection: RTCPeerConnection!, iceGatheringChanged newState: RTCICEGatheringState) { } - func peerConnection(peerConnection: RTCPeerConnection!, gotICECandidate candidate: RTCICECandidate!) { + func peerConnection(_ peerConnection: RTCPeerConnection!, gotICECandidate candidate: RTCICECandidate!) { onCandidateReceived?(candidate) } - func peerConnection(peerConnection: RTCPeerConnection!, didOpenDataChannel dataChannel: RTCDataChannel!) { + func peerConnection(_ peerConnection: RTCPeerConnection!, didOpen dataChannel: RTCDataChannel!) { } } class AASessionDescriptionCreateDelegate: NSObject, RTCSessionDescriptionDelegate { - let didCreate: (RTCSessionDescription!, NSError!) -> () + let didCreate: (RTCSessionDescription?, Error?) -> () let peerConnection: RTCPeerConnection - init(didCreate: (RTCSessionDescription!, NSError!) -> (), peerConnection: RTCPeerConnection) { + init(didCreate: @escaping (RTCSessionDescription?, Error?) -> (), peerConnection: RTCPeerConnection) { self.didCreate = didCreate self.peerConnection = peerConnection } - func peerConnection(peerConnection: RTCPeerConnection!, didCreateSessionDescription sdp: RTCSessionDescription!, error: NSError!) { + func peerConnection(_ peerConnection: RTCPeerConnection!, didCreateSessionDescription sdp: RTCSessionDescription!, error: Error!) { didCreate(sdp!, error) } - func peerConnection(peerConnection: RTCPeerConnection!, didSetSessionDescriptionWithError error: NSError!) { + func peerConnection(_ peerConnection: RTCPeerConnection!, didSetSessionDescriptionWithError error: Error!) { } } @@ -65,10 +65,10 @@ class AASessionDescriptionCreateDelegate: NSObject, RTCSessionDescriptionDelegat private var sessionSetTarget = "descTarget" class AASessionDescriptionSetDelegate: NSObject, RTCSessionDescriptionDelegate { - let didSet: (NSError!) -> () + let didSet: (Error!) -> () let peerConnection: RTCPeerConnection - init(didSet: (NSError!) -> (), peerConnection: RTCPeerConnection) { + init(didSet: @escaping (Error!) -> (), peerConnection: RTCPeerConnection) { self.didSet = didSet self.peerConnection = peerConnection super.init() @@ -76,11 +76,11 @@ class AASessionDescriptionSetDelegate: NSObject, RTCSessionDescriptionDelegate { setAssociatedObject(peerConnection, value: self, associativeKey: &sessionSetTarget) } - func peerConnection(peerConnection: RTCPeerConnection!, didCreateSessionDescription sdp: RTCSessionDescription!, error: NSError!) { + func peerConnection(_ peerConnection: RTCPeerConnection!, didCreateSessionDescription sdp: RTCSessionDescription!, error: Error!) { } - func peerConnection(peerConnection: RTCPeerConnection!, didSetSessionDescriptionWithError error: NSError!) { + func peerConnection(_ peerConnection: RTCPeerConnection!, didSetSessionDescriptionWithError error: Error!) { setAssociatedObject(peerConnection, value: "", associativeKey: &sessionSetTarget) @@ -109,7 +109,7 @@ extension RTCPeerConnection { } } - private func intDelegate() -> AAPeerConnectionDelegate { + fileprivate func intDelegate() -> AAPeerConnectionDelegate { let stored = self.delegate as? AAPeerConnectionDelegate if (stored != nil) { return stored! @@ -121,20 +121,20 @@ extension RTCPeerConnection { return nDelegate } - func createAnswer(constraints: RTCMediaConstraints, didCreate: (RTCSessionDescription!, NSError!) -> ()) { - createAnswerWithDelegate(AASessionDescriptionCreateDelegate(didCreate: didCreate, peerConnection: self), constraints: constraints) + func createAnswer(_ constraints: RTCMediaConstraints, didCreate: @escaping (RTCSessionDescription?, Error?) -> ()) { + self.createAnswer(with: AASessionDescriptionCreateDelegate(didCreate: didCreate, peerConnection: self), constraints: constraints) } - func createOffer(constraints: RTCMediaConstraints, didCreate: (RTCSessionDescription!, NSError!) -> ()) { - createOfferWithDelegate(AASessionDescriptionCreateDelegate(didCreate: didCreate, peerConnection: self), constraints: constraints) + func createOffer(_ constraints: RTCMediaConstraints, didCreate: @escaping (RTCSessionDescription?, Error?) -> ()) { + self.createOffer(with: AASessionDescriptionCreateDelegate(didCreate: didCreate, peerConnection: self), constraints: constraints) } - func setLocalDescription(sdp: RTCSessionDescription, didSet: (NSError!) -> ()) { - setLocalDescriptionWithDelegate(AASessionDescriptionSetDelegate(didSet: didSet, peerConnection: self), sessionDescription: sdp) + func setLocalDescription(_ sdp: RTCSessionDescription, didSet: @escaping (Error!) -> ()) { + setLocalDescriptionWith(AASessionDescriptionSetDelegate(didSet: didSet, peerConnection: self), sessionDescription: sdp) } - func setRemoteDescription(sdp: RTCSessionDescription, didSet: (NSError!) -> ()) { - setRemoteDescriptionWithDelegate(AASessionDescriptionSetDelegate(didSet: didSet, peerConnection: self), sessionDescription: sdp) + func setRemoteDescription(_ sdp: RTCSessionDescription, didSet: @escaping (Error!) -> ()) { + setRemoteDescriptionWith(AASessionDescriptionSetDelegate(didSet: didSet, peerConnection: self), sessionDescription: sdp) } } diff --git a/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/power/WakeLock.java b/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/power/WakeLock.java index bf29d92cd8..ffcb0b1dc8 100644 --- a/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/power/WakeLock.java +++ b/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/power/WakeLock.java @@ -4,6 +4,7 @@ public interface WakeLock { - @ObjectiveCName("releaseLock") + // Don't use "release" prefix as it is conflicts with ObjC runtime + @ObjectiveCName("closeLock") void releaseLock(); } From 306a24a28a2ca722ea82e1549600e9a5b24e1b6e Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Fri, 23 Sep 2016 02:21:24 +0300 Subject: [PATCH 362/414] fix(iOS): Fixing Reachability --- .../ActorApp.xcodeproj/project.pbxproj | 2 +- .../ActorSDK.xcodeproj/project.pbxproj | 6 +- .../ActorSDK/Sources/ActorSDK.swift | 75 ++-- .../ActorSDK/Sources/Utils/Reachability.swift | 389 ------------------ actor-sdk/sdk-core-ios/Podfile | 2 + 5 files changed, 41 insertions(+), 433 deletions(-) delete mode 100644 actor-sdk/sdk-core-ios/ActorSDK/Sources/Utils/Reachability.swift diff --git a/actor-sdk/sdk-core-ios/ActorApp.xcodeproj/project.pbxproj b/actor-sdk/sdk-core-ios/ActorApp.xcodeproj/project.pbxproj index 3a06e8a7fb..48b7ede315 100644 --- a/actor-sdk/sdk-core-ios/ActorApp.xcodeproj/project.pbxproj +++ b/actor-sdk/sdk-core-ios/ActorApp.xcodeproj/project.pbxproj @@ -301,7 +301,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; + shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; showEnvVarsInLog = 0; }; 851EE1A34619AE6677649A27 /* Embed Pods Frameworks */ = { diff --git a/actor-sdk/sdk-core-ios/ActorSDK.xcodeproj/project.pbxproj b/actor-sdk/sdk-core-ios/ActorSDK.xcodeproj/project.pbxproj index 7eb0fa4600..1074d8b8b3 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK.xcodeproj/project.pbxproj +++ b/actor-sdk/sdk-core-ios/ActorSDK.xcodeproj/project.pbxproj @@ -243,7 +243,6 @@ 06C1D0771C8BC9FC00B73632 /* AAAuthNameViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06C1D0761C8BC9FC00B73632 /* AAAuthNameViewController.swift */; }; 06C1D07B1C8BFE5C00B73632 /* AAAuthPhoneViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06C1D07A1C8BFE5C00B73632 /* AAAuthPhoneViewController.swift */; }; 06C1D07E1C8D0DEA00B73632 /* Telephony.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06C1D07D1C8D0DE900B73632 /* Telephony.swift */; }; - 06CE898A1BD8401C005A5530 /* Reachability.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06CE89891BD8401C005A5530 /* Reachability.swift */; }; 06CE898C1BD841C9005A5530 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 06CE898B1BD841C9005A5530 /* SystemConfiguration.framework */; }; 06CE89901BD84DF5005A5530 /* ActorSDKAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06CE898F1BD84DF5005A5530 /* ActorSDKAnalytics.swift */; }; 06D5C0571C8D6E20002D5045 /* AAAuthLogInViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06D5C0561C8D6E20002D5045 /* AAAuthLogInViewController.swift */; }; @@ -621,7 +620,6 @@ 06C1D0761C8BC9FC00B73632 /* AAAuthNameViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AAAuthNameViewController.swift; sourceTree = ""; }; 06C1D07A1C8BFE5C00B73632 /* AAAuthPhoneViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AAAuthPhoneViewController.swift; sourceTree = ""; }; 06C1D07D1C8D0DE900B73632 /* Telephony.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Telephony.swift; sourceTree = ""; }; - 06CE89891BD8401C005A5530 /* Reachability.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Reachability.swift; sourceTree = ""; }; 06CE898B1BD841C9005A5530 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; }; 06CE898F1BD84DF5005A5530 /* ActorSDKAnalytics.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActorSDKAnalytics.swift; sourceTree = ""; }; 06D5C0561C8D6E20002D5045 /* AAAuthLogInViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AAAuthLogInViewController.swift; sourceTree = ""; }; @@ -986,7 +984,6 @@ 066A51581BC4C14A000E606E /* AASwiftlyLRU.swift */, 066A51641BC4C366000E606E /* AATools.swift */, 065975381BC7CA7B00B8C7DF /* Bundle.swift */, - 06CE89891BD8401C005A5530 /* Reachability.swift */, 06C1D07D1C8D0DE900B73632 /* Telephony.swift */, ); path = Utils; @@ -2021,7 +2018,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; + shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; showEnvVarsInLog = 0; }; A2ED258362D73946D3AE7FB4 /* [CP] Copy Pods Resources */ = { @@ -2263,7 +2260,6 @@ 061850ED1C95CBF000C522D5 /* YYTextArchiver.m in Sources */, 066A50E21BC4AF9F000E606E /* ActorSDK.swift in Sources */, 15D35F761C201B6B00E3717A /* AACustomPresentationAnimationController.swift in Sources */, - 06CE898A1BD8401C005A5530 /* Reachability.swift in Sources */, 06E7B24C1C0FAB500090660C /* AAMapFastView.swift in Sources */, BED5A1F11C48396A0045FDB0 /* NYTPhotoCaptionView.m in Sources */, BED5A20F1C4839880045FDB0 /* NSBundle+NYTPhotoViewer.m in Sources */, diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorSDK.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorSDK.swift index afdd9ab22b..c033882881 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorSDK.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorSDK.swift @@ -7,6 +7,7 @@ import JDStatusBarNotification import PushKit import SafariServices import DZNWebViewController +import ReachabilitySwift @objc open class ActorSDK: NSObject, PKPushRegistryDelegate { @@ -208,7 +209,7 @@ import DZNWebViewController fileprivate(set) open var bindedToWindow: UIWindow! // Reachability - // fileprivate var reachability: Reachability! + fileprivate var reachability: Reachability! public override init() { @@ -227,65 +228,65 @@ import DZNWebViewController AAActorRuntime.configureRuntime() - let builder = ACConfigurationBuilder() + let builder = ACConfigurationBuilder()! // Api Connections let deviceKey = UUID().uuidString let deviceName = UIDevice.current.name let appTitle = "Actor iOS" for url in endpoints { - builder?.addEndpoint(url) + builder.addEndpoint(url) } for key in trustedKeys { - builder?.addTrustedKey(key) + builder.addTrustedKey(key) } - builder?.setApiConfiguration(ACApiConfiguration(appTitle: appTitle, withAppId: jint(apiId), withAppKey: apiKey, withDeviceTitle: deviceName, withDeviceId: deviceKey)) + builder.setApiConfiguration(ACApiConfiguration(appTitle: appTitle, withAppId: jint(apiId), withAppKey: apiKey, withDeviceTitle: deviceName, withDeviceId: deviceKey)) // Providers - builder?.setPhoneBookProvider(PhoneBookProvider()) - builder?.setNotificationProvider(iOSNotificationProvider()) - builder?.setCallsProvider(iOSCallsProvider()) + builder.setPhoneBookProvider(PhoneBookProvider()) + builder.setNotificationProvider(iOSNotificationProvider()) + builder.setCallsProvider(iOSCallsProvider()) // Stats - builder?.setPlatformType(ACPlatformType.ios()) - builder?.setDeviceCategory(ACDeviceCategory.mobile()) + builder.setPlatformType(ACPlatformType.ios()) + builder.setDeviceCategory(ACDeviceCategory.mobile()) // Locale for lang in Locale.preferredLanguages { log("Found locale :\(lang)") - builder?.addPreferredLanguage(lang) + builder.addPreferredLanguage(lang) } // TimeZone let timeZone = TimeZone.current.identifier log("Found time zone :\(timeZone)") - builder?.setTimeZone(timeZone) + builder.setTimeZone(timeZone) // AutoJoin for s in autoJoinGroups { - builder?.addAutoJoinGroup(withToken: s) + builder.addAutoJoinGroup(withToken: s) } if autoJoinOnReady { - builder?.setAutoJoinType(ACAutoJoinType.after_INIT()) + builder.setAutoJoinType(ACAutoJoinType.after_INIT()) } else { - builder?.setAutoJoinType(ACAutoJoinType.immediately()) + builder.setAutoJoinType(ACAutoJoinType.immediately()) } // Logs // builder.setEnableFilesLogging(true) // Application name - builder?.setCustomAppName(appName) + builder.setCustomAppName(appName) // Config - builder?.setPhoneBookImportEnabled(jboolean(enablePhoneBookImport)) - builder?.setVoiceCallsEnabled(jboolean(enableCalls)) - builder?.setVideoCallsEnabled(jboolean(enableCalls)) - builder?.setIsEnabledGroupedChatList(false) + builder.setPhoneBookImportEnabled(jboolean(enablePhoneBookImport)) + builder.setVoiceCallsEnabled(jboolean(enableCalls)) + builder.setVideoCallsEnabled(jboolean(enableCalls)) + builder.setIsEnabledGroupedChatList(false) // builder.setEnableFilesLogging(true) // Creating messenger - messenger = ACCocoaMessenger(configuration: (builder?.build())!) + messenger = ACCocoaMessenger(configuration: builder.build()) // Configure bubbles AABubbles.layouters = delegate.actorConfigureBubbleLayouters(AABubbles.builtInLayouters) @@ -331,23 +332,21 @@ import DZNWebViewController // Subscribe to network changes -// do { -// reachability = try Reachability.reachabilityForInternetConnection() -// NotificationCenter.default.addObserver(self, selector: #selector(ActorSDK.reachabilityChanged(_:)), name: NSNotification.Name(rawValue: ReachabilityChangedNotification), object: reachability) -// try reachability.startNotifier() -// } catch { -// print("Unable to create Reachability") -// return -// } - } - -// @objc func reachabilityChanged(_ note: Notification) { -// print("reachabilityChanged (\(reachability.isReachable()))") -// -// if reachability.isReachable() { -// messenger.forceNetworkCheck() -// } -// } + reachability = Reachability()! + if reachability != nil { + reachability.whenReachable = { reachability in + self.messenger.forceNetworkCheck() + } + + do { + try reachability.startNotifier() + } catch { + print("Unable to start Reachability") + } + } else { + print("Unable to create Reachability") + } + } func didLoggedIn() { diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Utils/Reachability.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Utils/Reachability.swift deleted file mode 100644 index 8cf8d1c85b..0000000000 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Utils/Reachability.swift +++ /dev/null @@ -1,389 +0,0 @@ -///* -//Copyright (c) 2014, Ashley Mills -//All rights reserved. -// -//Redistribution and use in source and binary forms, with or without -//modification, are permitted provided that the following conditions are met: -// -//1. Redistributions of source code must retain the above copyright notice, this -//list of conditions and the following disclaimer. -// -//2. Redistributions in binary form must reproduce the above copyright notice, -//this list of conditions and the following disclaimer in the documentation -//and/or other materials provided with the distribution. -// -//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -//AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -//IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -//ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -//LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -//CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -//SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -//INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -//CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -//ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -//POSSIBILITY OF SUCH DAMAGE. -//*/ -// -//import SystemConfiguration -//import Foundation -// -//enum ReachabilityError: Error { -// case failedToCreateWithAddress(sockaddr_in) -// case failedToCreateWithHostname(String) -// case unableToSetCallback -// case unableToSetDispatchQueue -//} -// -//public let ReachabilityChangedNotification = "ReachabilityChangedNotification" -// -//func callback(_ reachability:SCNetworkReachability, flags: SCNetworkReachabilityFlags, info: UnsafeMutableRawPointer) { -//// let reachability = Unmanaged.fromOpaque(OpaquePointer(info)).takeUnretainedValue() -//// -//// DispatchQueue.main.async { -//// reachability.reachabilityChanged(flags) -//// } -//} -// -// -//open class Reachability: NSObject { -// -// public typealias NetworkReachable = (Reachability) -> () -// public typealias NetworkUnreachable = (Reachability) -> () -// -// public enum NetworkStatus: CustomStringConvertible { -// -// case notReachable, reachableViaWiFi, reachableViaWWAN -// -// public var description: String { -// switch self { -// case .reachableViaWWAN: -// return "Cellular" -// case .reachableViaWiFi: -// return "WiFi" -// case .notReachable: -// return "No Connection" -// } -// } -// } -// -// // MARK: - *** Public properties *** -// -// open var whenReachable: NetworkReachable? -// open var whenUnreachable: NetworkUnreachable? -// open var reachableOnWWAN: Bool -// open var notificationCenter = NotificationCenter.default -// -// open var currentReachabilityStatus: NetworkStatus { -// if isReachable() { -// if isReachableViaWiFi() { -// return .reachableViaWiFi -// } -// if isRunningOnDevice { -// return .reachableViaWWAN -// } -// } -// -// return .notReachable -// } -// -// open var currentReachabilityString: String { -// return "\(currentReachabilityStatus)" -// } -// -// // MARK: - *** Initialisation methods *** -// -// required public init(reachabilityRef: SCNetworkReachability) { -// reachableOnWWAN = true -// self.reachabilityRef = reachabilityRef -// } -// -// public convenience init(hostname: String) throws { -// -// let nodename = (hostname as NSString).utf8String -// guard let ref = SCNetworkReachabilityCreateWithName(nil, nodename!) else { throw ReachabilityError.failedToCreateWithHostname(hostname) } -// -// self.init(reachabilityRef: ref) -// } -// -// open class func reachabilityForInternetConnection() throws -> Reachability { -// -// var zeroAddress = sockaddr_in() -// zeroAddress.sin_len = UInt8(MemoryLayout.size(ofValue: zeroAddress)) -// zeroAddress.sin_family = sa_family_t(AF_INET) -// -//// guard let ref = withUnsafePointer(to: &zeroAddress, { -//// SCNetworkReachabilityCreateWithAddress(nil, UnsafePointer($0)) -//// }) else { throw ReachabilityError.failedToCreateWithAddress(zeroAddress) } -// -// // return Reachability(reachabilityRef: ref) -// -// } -// -// open class func reachabilityForLocalWiFi() throws -> Reachability { -// -// var localWifiAddress: sockaddr_in = sockaddr_in(sin_len: __uint8_t(0), sin_family: sa_family_t(0), sin_port: in_port_t(0), sin_addr: in_addr(s_addr: 0), sin_zero: (0, 0, 0, 0, 0, 0, 0, 0)) -// localWifiAddress.sin_len = UInt8(MemoryLayout.size(ofValue: localWifiAddress)) -// localWifiAddress.sin_family = sa_family_t(AF_INET) -// -// // IN_LINKLOCALNETNUM is defined in as 169.254.0.0 -// let address: UInt32 = 0xA9FE0000 -// localWifiAddress.sin_addr.s_addr = in_addr_t(address.bigEndian) -// -//// guard let ref = withUnsafePointer(to: &localWifiAddress, { -//// SCNetworkReachabilityCreateWithAddress(nil, UnsafePointer($0)) -//// }) else { throw ReachabilityError.failedToCreateWithAddress(localWifiAddress) } -// -// // return Reachability(reachabilityRef: ref) -// } -// -// // MARK: - *** Notifier methods *** -// open func startNotifier() throws { -// -// if notifierRunning { return } -// -//// var context = SCNetworkReachabilityContext(version: 0, info: nil, retain: nil, release: nil, copyDescription: nil) -//// context.info = UnsafeMutablePointer(Unmanaged.passUnretained(self).toOpaque()) -//// -//// if !SCNetworkReachabilitySetCallback(reachabilityRef!, callback, &context) { -//// stopNotifier() -//// throw ReachabilityError.unableToSetCallback -//// } -//// -//// if !SCNetworkReachabilitySetDispatchQueue(reachabilityRef!, reachabilitySerialQueue) { -//// stopNotifier() -//// throw ReachabilityError.unableToSetDispatchQueue -//// } -// -// notifierRunning = true -// } -// -// -// open func stopNotifier() { -// if let reachabilityRef = reachabilityRef { -// SCNetworkReachabilitySetCallback(reachabilityRef, nil, nil) -// SCNetworkReachabilitySetDispatchQueue(reachabilityRef, nil) -// } -// notifierRunning = false -// } -// -// // MARK: - *** Connection test methods *** -// open func isReachable() -> Bool { -// return isReachableWithTest({ (flags: SCNetworkReachabilityFlags) -> (Bool) in -// return self.isReachableWithFlags(flags) -// }) -// } -// -// open func isReachableViaWWAN() -> Bool { -// -// if isRunningOnDevice { -// return isReachableWithTest() { flags -> Bool in -// // Check we're REACHABLE -// if self.isReachable(flags) { -// -// // Now, check we're on WWAN -// if self.isOnWWAN(flags) { -// return true -// } -// } -// return false -// } -// } -// return false -// } -// -// open func isReachableViaWiFi() -> Bool { -// -// return isReachableWithTest() { flags -> Bool in -// -// // Check we're reachable -// if self.isReachable(flags) { -// -// if self.isRunningOnDevice { -// // Check we're NOT on WWAN -// if self.isOnWWAN(flags) { -// return false -// } -// } -// return true -// } -// -// return false -// } -// } -// -// // MARK: - *** Private methods *** -// fileprivate var isRunningOnDevice: Bool = { -// #if (arch(i386) || arch(x86_64)) && os(iOS) -// return false -// #else -// return true -// #endif -// }() -// -// fileprivate var notifierRunning = false -// fileprivate var reachabilityRef: SCNetworkReachability? -// fileprivate let reachabilitySerialQueue = DispatchQueue(label: "uk.co.ashleymills.reachability", attributes: []) -// -// fileprivate func reachabilityChanged(_ flags: SCNetworkReachabilityFlags) { -// if isReachableWithFlags(flags) { -// if let block = whenReachable { -// block(self) -// } -// } else { -// if let block = whenUnreachable { -// block(self) -// } -// } -// -// notificationCenter.post(name: Notification.Name(rawValue: ReachabilityChangedNotification), object:self) -// } -// -// fileprivate func isReachableWithFlags(_ flags: SCNetworkReachabilityFlags) -> Bool { -// -// let reachable = isReachable(flags) -// -// if !reachable { -// return false -// } -// -// if isConnectionRequiredOrTransient(flags) { -// return false -// } -// -// if isRunningOnDevice { -// if isOnWWAN(flags) && !reachableOnWWAN { -// // We don't want to connect when on 3G. -// return false -// } -// } -// -// return true -// } -// -// fileprivate func isReachableWithTest(_ test: (SCNetworkReachabilityFlags) -> (Bool)) -> Bool { -// -// if let reachabilityRef = reachabilityRef { -// -// var flags = SCNetworkReachabilityFlags(rawValue: 0) -// let gotFlags = withUnsafeMutablePointer(to: &flags) { -// SCNetworkReachabilityGetFlags(reachabilityRef, UnsafeMutablePointer($0)) -// } -// -// if gotFlags { -// return test(flags) -// } -// } -// -// return false -// } -// -// // WWAN may be available, but not active until a connection has been established. -// // WiFi may require a connection for VPN on Demand. -// fileprivate func isConnectionRequired() -> Bool { -// return connectionRequired() -// } -// -// fileprivate func connectionRequired() -> Bool { -// return isReachableWithTest({ (flags: SCNetworkReachabilityFlags) -> (Bool) in -// return self.isConnectionRequired(flags) -// }) -// } -// -// // Dynamic, on demand connection? -// fileprivate func isConnectionOnDemand() -> Bool { -// return isReachableWithTest({ (flags: SCNetworkReachabilityFlags) -> (Bool) in -// return self.isConnectionRequired(flags) && self.isConnectionOnTrafficOrDemand(flags) -// }) -// } -// -// // Is user intervention required? -// fileprivate func isInterventionRequired() -> Bool { -// return isReachableWithTest({ (flags: SCNetworkReachabilityFlags) -> (Bool) in -// return self.isConnectionRequired(flags) && self.isInterventionRequired(flags) -// }) -// } -// -// fileprivate func isOnWWAN(_ flags: SCNetworkReachabilityFlags) -> Bool { -// #if os(iOS) -// return flags.contains(.isWWAN) -// #else -// return false -// #endif -// } -// fileprivate func isReachable(_ flags: SCNetworkReachabilityFlags) -> Bool { -// return flags.contains(.reachable) -// } -// fileprivate func isConnectionRequired(_ flags: SCNetworkReachabilityFlags) -> Bool { -// return flags.contains(.connectionRequired) -// } -// fileprivate func isInterventionRequired(_ flags: SCNetworkReachabilityFlags) -> Bool { -// return flags.contains(.interventionRequired) -// } -// fileprivate func isConnectionOnTraffic(_ flags: SCNetworkReachabilityFlags) -> Bool { -// return flags.contains(.connectionOnTraffic) -// } -// fileprivate func isConnectionOnDemand(_ flags: SCNetworkReachabilityFlags) -> Bool { -// return flags.contains(.connectionOnDemand) -// } -// func isConnectionOnTrafficOrDemand(_ flags: SCNetworkReachabilityFlags) -> Bool { -// return !flags.intersection([.connectionOnTraffic, .connectionOnDemand]).isEmpty -// } -// fileprivate func isTransientConnection(_ flags: SCNetworkReachabilityFlags) -> Bool { -// return flags.contains(.transientConnection) -// } -// fileprivate func isLocalAddress(_ flags: SCNetworkReachabilityFlags) -> Bool { -// return flags.contains(.isLocalAddress) -// } -// fileprivate func isDirect(_ flags: SCNetworkReachabilityFlags) -> Bool { -// return flags.contains(.isDirect) -// } -// fileprivate func isConnectionRequiredOrTransient(_ flags: SCNetworkReachabilityFlags) -> Bool { -// let testcase:SCNetworkReachabilityFlags = [.connectionRequired, .transientConnection] -// return flags.intersection(testcase) == testcase -// } -// -// fileprivate var reachabilityFlags: SCNetworkReachabilityFlags { -// if let reachabilityRef = reachabilityRef { -// -// var flags = SCNetworkReachabilityFlags(rawValue: 0) -// let gotFlags = withUnsafeMutablePointer(to: &flags) { -// SCNetworkReachabilityGetFlags(reachabilityRef, UnsafeMutablePointer($0)) -// } -// -// if gotFlags { -// return flags -// } -// } -// -// return [] -// } -// -// override open var description: String { -// -// var W: String -// if isRunningOnDevice { -// W = isOnWWAN(reachabilityFlags) ? "W" : "-" -// } else { -// W = "X" -// } -// let R = isReachable(reachabilityFlags) ? "R" : "-" -// let c = isConnectionRequired(reachabilityFlags) ? "c" : "-" -// let t = isTransientConnection(reachabilityFlags) ? "t" : "-" -// let i = isInterventionRequired(reachabilityFlags) ? "i" : "-" -// let C = isConnectionOnTraffic(reachabilityFlags) ? "C" : "-" -// let D = isConnectionOnDemand(reachabilityFlags) ? "D" : "-" -// let l = isLocalAddress(reachabilityFlags) ? "l" : "-" -// let d = isDirect(reachabilityFlags) ? "d" : "-" -// -// return "\(W)\(R) \(c)\(t)\(i)\(C)\(D)\(l)\(d)" -// } -// -// deinit { -// stopNotifier() -// -// reachabilityRef = nil -// whenReachable = nil -// whenUnreachable = nil -// } -//} diff --git a/actor-sdk/sdk-core-ios/Podfile b/actor-sdk/sdk-core-ios/Podfile index c36114cfb4..149cff30ba 100644 --- a/actor-sdk/sdk-core-ios/Podfile +++ b/actor-sdk/sdk-core-ios/Podfile @@ -14,6 +14,7 @@ target 'ActorApp' do pod 'RegexKitLite' # pod 'CocoaAsyncSocket' pod 'zipzap' + pod 'ReachabilitySwift', '~> 3' # Main UI pod 'TTTAttributedLabel' @@ -47,6 +48,7 @@ target 'ActorSDK' do pod 'RegexKitLite' # pod 'CocoaAsyncSocket' pod 'zipzap' + pod 'ReachabilitySwift', '~> 3' # Main UI pod 'TTTAttributedLabel' From 5e7680e935789358ccc1601c1385566199bde133 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Fri, 23 Sep 2016 02:21:53 +0300 Subject: [PATCH 363/414] fix(iOS): Fixing push receive handling --- actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorSDK.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorSDK.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorSDK.swift index c033882881..a91ef5ab1e 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorSDK.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorSDK.swift @@ -445,7 +445,7 @@ import ReachabilitySwift Actor.checkCall(jlong(callId)!, withAttempt: 0) } } else if let seq = aps["seq"] as? String { - // Actor.onPushReceivedWithSeq(jint(seq)!) + Actor.onPushReceived(withSeq: jint(seq)!, withAuthId: 0) } } } From 8f3e8eec15c06ca48e67240f748cae40db1075f9 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Fri, 23 Sep 2016 02:38:13 +0300 Subject: [PATCH 364/414] fix(iOS): Fixing actor core and string extensions --- .../Sources/ActorCore/ActorCoreExt.swift | 132 +----------------- .../Sources/SwiftExtensions/Strings.swift | 32 ++--- 2 files changed, 21 insertions(+), 143 deletions(-) diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/ActorCoreExt.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/ActorCoreExt.swift index 8e52a80ac0..227689bf8b 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/ActorCoreExt.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/ActorCoreExt.swift @@ -271,19 +271,6 @@ class AAFileCallback : NSObject, ACFileCallback { // Markdown // -//public class ARMDFormattedText { -// -// public let isTrivial: Bool -// public let attributedText: NSAttributedString -// public let code: [String] -// -// public init(attributedText: NSAttributedString, isTrivial: Bool, code: [String]) { -// self.attributedText = attributedText -// self.code = code -// self.isTrivial = isTrivial -// } -//} - open class TextParser { open let textColor: UIColor @@ -316,7 +303,7 @@ open class TextParser { var isFirst = true for s in sections { if !isFirst { - // nAttrText.append(NSAttributedString(string: "\n")) + nAttrText.append(NSAttributedString(string: "\n")) } isFirst = false @@ -331,13 +318,13 @@ open class TextParser { str.yy_setFont(UIFont.textFontOfSize(fontSize), range: range) str.yy_setColor(linkColor, range: range) - // nAttrText.append(str) + nAttrText.append(str) sources.append(s.getCode().getCode()) } else if s.getType() == ARMDSection_TYPE_TEXT { let child: [ARMDText] = s.getText().toSwiftArray() for c in child { - // nAttrText.append(buildText(c, fontSize: fontSize)) + nAttrText.append(buildText(c, fontSize: fontSize)) } } else { fatalError("Unsupported section type") @@ -363,7 +350,7 @@ open class TextParser { // Processing child texts let child: [ARMDText] = span.getChild().toSwiftArray() for c in child { - // res.append(buildText(c, fontSize: fontSize)) + res.append(buildText(c, fontSize: fontSize)) } // Setting span elements @@ -418,116 +405,7 @@ open class ParsedText { self.isTrivial = isTrivial } } -// -//public extension ARMarkdownParser { -// -// public func parse(text: String, textColor: UIColor, fontSize: CGFloat) -> ARMDFormattedText { -// -// let doc = self.processDocumentWithNSString(text) -// if doc.isTrivial() { -// let nAttrText = NSMutableAttributedString(string: text) -// let range = NSRange(location: 0, length: nAttrText.length) -// nAttrText.yy_setColor(textColor, range: range) -// nAttrText.yy_setFont(UIFont.textFontOfSize(fontSize), range: range) -// return ARMDFormattedText(attributedText: nAttrText, isTrivial: true, code: []) -// } -// -// var sources = [String]() -// -// let sections: [ARMDSection] = doc.getSections().toSwiftArray() -// let nAttrText = NSMutableAttributedString() -// var isFirst = true -// for s in sections { -// if !isFirst { -// nAttrText.appendAttributedString(NSAttributedString(string: "\n")) -// } -// isFirst = false -// -// if s.getType() == ARMDSection_TYPE_CODE { -// let attributes = [NSLinkAttributeName: NSURL(string: "source:///\(sources.count)") as! AnyObject, -// NSFontAttributeName: UIFont.textFontOfSize(fontSize)] -// nAttrText.appendAttributedString(NSAttributedString(string: "Open Code", attributes: attributes)) -// sources.append(s.getCode().getCode()) -// } else if s.getType() == ARMDSection_TYPE_TEXT { -// let child: [ARMDText] = s.getText().toSwiftArray() -// for c in child { -// nAttrText.appendAttributedString(buildText(c, fontSize: fontSize)) -// } -// } else { -// fatalError("Unsupported section type") -// } -// } -// -//// let range = NSRange(location: 0, length: nAttrText.length) -// -//// nAttrText.yy_setColor(textColor, range: range) -//// nAttrText.yy_setFont(UIFont.textFontOfSize(fontSize), range: range) -// -//// nAttrText.enumerateAttributesInRange(range, options: NSAttributedStringEnumerationOptions.LongestEffectiveRangeNotRequired) { (attrs, range, objBool) -> Void in -//// var attributeDictionary = NSDictionary(dictionary: attrs) -//// -//// for k in attributeDictionary.allKeys { -//// let v = attributeDictionary.objectForKey(k) -//// -//// print("attr: \(k) -> \(v) at \(range)") -//// } -//// } -//// -// return ARMDFormattedText(attributedText: nAttrText, isTrivial: false, code: sources) -// } -// -// private func buildText(text: ARMDText, fontSize: CGFloat) -> NSAttributedString { -// if let raw = text as? ARMDRawText { -//// let res = NSMutableAttributedString(string: raw.getRawText()) -//// res.yy_setFont(UIFont.textFontOfSize(fontSize), range: NSRange(location: 0, length: raw.getRawText().length)) -//// return res -// return NSAttributedString(string: raw.getRawText(), font: UIFont.textFontOfSize(fontSize)) -// } else if let span = text as? ARMDSpan { -// let res = NSMutableAttributedString() -// res.beginEditing() -// -// // Processing child texts -// let child: [ARMDText] = span.getChild().toSwiftArray() -// for c in child { -// res.appendAttributedString(buildText(c, fontSize: fontSize)) -// } -// -// // Setting span elements -// if span.getSpanType() == ARMDSpan_TYPE_BOLD { -// res.appendFont(UIFont.boldSystemFontOfSize(fontSize)) -// } else if span.getSpanType() == ARMDSpan_TYPE_ITALIC { -// res.appendFont(UIFont.italicSystemFontOfSize(fontSize)) -// } else { -// fatalError("Unsupported span type") -// } -// -// res.endEditing() -// return res -// } else if let url = text as? ARMDUrl { -// -// // Parsing url element -// let nsUrl = NSURL(string: url.getUrl()) -// if nsUrl != nil { -// let res = NSMutableAttributedString(string: url.getUrlTitle()) -// let range = NSRange(location: 0, length: res.length) -// let highlight = YYTextHighlight() -//// res.yy_setFont(UIFont.textFontOfSize(fontSize), range: range) -//// res.yy_setTextHighlightRange(range, color: UIColor.redColor(), backgroundColor: nil, tapAction: nil) -// // res.yy_setColor(UIColor.greenColor(), range: range) -// return res -//// let attributes = [NSLinkAttributeName: nsUrl as! AnyObject, -//// NSFontAttributeName: UIFont.textFontOfSize(fontSize)] -//// return NSAttributedString(string: url.getUrlTitle(), attributes: attributes) -// } else { -// // Unable to parse: show as text -// return NSAttributedString(string: url.getUrlTitle(), font: UIFont.textFontOfSize(fontSize)) -// } -// } else { -// fatalError("Unsupported text type") -// } -// } -//} -// + // // Promises // diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/SwiftExtensions/Strings.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/SwiftExtensions/Strings.swift index 65a8c2e474..bc70feb3f2 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/SwiftExtensions/Strings.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/SwiftExtensions/Strings.swift @@ -97,17 +97,17 @@ public extension String { public func rangesOfString(_ text: String) -> [Range] { var res = [Range]() -// var searchRange = (self.characters.indices) -// while true { -// let found = self.range(of: text, options: NSString.CompareOptions.caseInsensitive, range: searchRange, locale: nil) -// if found != nil { -// res.append(found!) -// searchRange = (found!.upperBound ..< self.endIndex) -// } else { -// break -// } -// } -// + var searchRange = (self.startIndex ..< self.endIndex) + while true { + let found = self.range(of: text, options: String.CompareOptions.caseInsensitive, range: searchRange, locale: nil) + if found != nil { + res.append(found!) + searchRange = (found!.upperBound ..< self.endIndex) + } else { + break + } + } + return res } @@ -141,15 +141,15 @@ public extension String { public extension NSAttributedString { - public func append(_ text: NSAttributedString) -> NSAttributedString { + public func appendMutate(_ text: NSAttributedString) -> NSAttributedString { let res = NSMutableAttributedString() - // res.append(self) - // res.append(text) + res.append(self) + res.append(text) return res } - public func append(_ text: String, font: UIFont) -> NSAttributedString { - return append(NSAttributedString(string: text, attributes: [NSFontAttributeName: font])) + public func appendMutate(_ text: String, font: UIFont) -> NSAttributedString { + return self.appendMutate(NSAttributedString(string: text, attributes: [NSFontAttributeName: font])) } public convenience init(string: String, font: UIFont) { From 493ad91b90ed63e5f54d93de0a0cb34583c5a8df Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Fri, 23 Sep 2016 04:25:53 +0300 Subject: [PATCH 365/414] feat(iOS): Restore features after migration --- .../ActorSDK.xcodeproj/project.pbxproj | 2 +- .../ActorCore/Providers/CocoaCrypto.swift | 9 ++-- .../Providers/CocoaFileSystemRuntime.swift | 52 ++++--------------- .../Providers/CocoaHttpRuntime.swift | 8 +-- .../Providers/CocoaNetworkRuntime.swift | 10 ++-- .../Providers/Storage/FMDBBridge.swift | 4 +- .../Providers/Storage/FMDBExtensions.swift | 36 ++++++------- .../Providers/Storage/FMDBKeyValue.swift | 4 +- .../Providers/Storage/FMDBList.swift | 20 +++---- .../Providers/iOSNotificationProvider.swift | 2 +- .../Auth/AAAuthEmailViewController.swift | 4 +- .../Auth/AAAuthLogInViewController.swift | 2 +- .../Auth/AAAuthNameViewController.swift | 2 +- .../Auth/AAAuthPhoneViewController.swift | 8 +-- .../Auth/AACountryViewController.swift | 22 ++++---- .../Calls/AACallViewController.swift | 2 +- .../Compose/AAComposeController.swift | 2 +- .../Compose/AAGroupCreateViewController.swift | 2 +- .../Compose/AAGroupMembersController.swift | 6 +-- .../Contacts/AAContactsViewController.swift | 4 +- .../Content/Input/AAEditFieldController.swift | 2 +- .../Content/Input/AAEditTextController.swift | 2 +- .../WebActions/AAWebActionController.swift | 2 +- .../AAAddParticipantViewController.swift | 2 +- .../Group/AAGroupEditInfoViewController.swift | 2 +- .../Group/AAGroupTypeController.swift | 4 +- .../Managed Runtime/AAViewController.swift | 7 +-- .../Controllers/Managed Runtime/Alerts.swift | 4 +- .../Managed Runtime/ManagedCells.swift | 8 ++- .../Settings/AASettingsViewController.swift | 6 +-- .../User/AAUserViewController.swift | 2 +- .../Sources/SwiftExtensions/AALocalized.swift | 5 +- .../ActorSDK/Sources/Views/AAAvatarView.swift | 8 +-- 33 files changed, 114 insertions(+), 141 deletions(-) diff --git a/actor-sdk/sdk-core-ios/ActorSDK.xcodeproj/project.pbxproj b/actor-sdk/sdk-core-ios/ActorSDK.xcodeproj/project.pbxproj index 1074d8b8b3..75f6063376 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK.xcodeproj/project.pbxproj +++ b/actor-sdk/sdk-core-ios/ActorSDK.xcodeproj/project.pbxproj @@ -1101,7 +1101,6 @@ 066A525F1BC50E53000E606E /* Controllers */ = { isa = PBXGroup; children = ( - 9AAE96461CF81F140092E366 /* Ringtones */, 06C1D0751C8BC55100B73632 /* Welcome */, 066A52601BC50E6B000E606E /* Auth */, 066A52F91BC52FA0000E606E /* Compose */, @@ -1111,6 +1110,7 @@ 06E7B2451C0F8D410090660C /* Location */, 066A53051BC5317B000E606E /* Recent */, 066A527B1BC51EC6000E606E /* Root */, + 9AAE96461CF81F140092E366 /* Ringtones */, 066A52EB1BC52AF8000E606E /* Settings */, 066A53091BC53197000E606E /* User */, 06E323131C6A7AA300D66F53 /* Calls */, diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/CocoaCrypto.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/CocoaCrypto.swift index 51468716ae..c1bf874299 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/CocoaCrypto.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/CocoaCrypto.swift @@ -44,10 +44,11 @@ class SHA256Digest: NSObject, ARDigest { func doFinal(_ dest: IOSByteArray!, withOffset destOffset: jint) { -// let pointer = UnsafeMutablePointer(dest.buffer()) -// .advanced(by: Int(destOffset)) -// -// CC_SHA256_Final(pointer, context) + let pointer = dest.buffer() + .advanced(by: Int(destOffset)) + .bindMemory(to: UInt8.self, capacity: Int(CC_SHA256_DIGEST_LENGTH)) + + CC_SHA256_Final(pointer, context) } func getSize() -> jint { diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/CocoaFileSystemRuntime.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/CocoaFileSystemRuntime.swift index f74c4eb3f5..a535530a83 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/CocoaFileSystemRuntime.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/CocoaFileSystemRuntime.swift @@ -99,8 +99,7 @@ class CocoaFile : NSObject, ARFileSystemReference { func getSize() -> jint { do { let attrs = try FileManager().attributesOfItem(atPath: realPath) - // return jint(Dictionary.fileSize(attrs)()) - return 0 + return jint((attrs[FileAttributeKey.size] as! NSNumber).int32Value) } catch _ { return 0 } @@ -169,20 +168,15 @@ class CocoaInputFile :NSObject, ARInputFile { return ARPromise { (resolver) in dispatchBackground { -// self.fileHandle.seek(toFileOffset: UInt64(fileOffset)) -// -// let readed: Data = self.fileHandle.readData(ofLength: Int(len)) -// let data = IOSByteArray(length: UInt(len)) -// var srcBuffer = UnsafeMutablePointer(mutating: (readed as NSData).bytes.bindMemory(to: UInt8.self, capacity: readed.count)) -// var destBuffer = UnsafeMutableRawPointer(data!.buffer()) -// let readCount = min(Int(len), Int(readed.count)) -// for _ in 0..(readed.bytes) -// var destBuffer = UnsafeMutablePointer(data.buffer()) -// let len = min(Int(len), Int(readed.length)) -// for _ in offset.. Bool { -// self.fileHandle.closeFile() -// return true -// } } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/CocoaHttpRuntime.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/CocoaHttpRuntime.swift index 7c22e18b7a..ec7ea7f187 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/CocoaHttpRuntime.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/CocoaHttpRuntime.swift @@ -19,7 +19,7 @@ class CocoaHttpRuntime: NSObject, ARHttpRuntime { request.setValue(header, forHTTPHeaderField: "Range") request.httpMethod = "GET" - NSURLConnection.sendAsynchronousRequest(request as URLRequest, queue: self.queue, completionHandler:{ (response: URLResponse?, data: Data?, error: NSError?) -> Void in + NSURLConnection.sendAsynchronousRequest(request as URLRequest, queue: self.queue, completionHandler:{ (response: URLResponse?, data: Data?, error: Error?) -> Void in if let respHttp = response as? HTTPURLResponse { if (respHttp.statusCode >= 200 && respHttp.statusCode < 300) { resolver.result(ARHTTPResponse(code: jint(respHttp.statusCode), withContent: data!.toJavaBytes())) @@ -29,7 +29,7 @@ class CocoaHttpRuntime: NSObject, ARHttpRuntime { } else { resolver.error(ARHTTPError(int: 0)) } - } as! (URLResponse?, Data?, Error?) -> Void) + }) } } @@ -42,7 +42,7 @@ class CocoaHttpRuntime: NSObject, ARHttpRuntime { request.httpBody = contents.toNSData() request.setValue("application/octet-stream", forHTTPHeaderField: "Content-Type") - NSURLConnection.sendAsynchronousRequest(request as URLRequest, queue: self.queue, completionHandler:{ (response: URLResponse?, data: Data?, error: NSError?) -> Void in + NSURLConnection.sendAsynchronousRequest(request as URLRequest, queue: self.queue, completionHandler:{ (response: URLResponse?, data: Data?, error: Error?) -> Void in if let respHttp = response as? HTTPURLResponse { if (respHttp.statusCode >= 200 && respHttp.statusCode < 300) { resolver.result(ARHTTPResponse(code: jint(respHttp.statusCode), withContent: nil)) @@ -52,7 +52,7 @@ class CocoaHttpRuntime: NSObject, ARHttpRuntime { } else { resolver.error(ARHTTPError(int: 0)) } - } as! (URLResponse?, Data?, Error?) -> Void) + }) } } } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/CocoaNetworkRuntime.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/CocoaNetworkRuntime.swift index 0af313ebf1..4d9351270e 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/CocoaNetworkRuntime.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/CocoaNetworkRuntime.swift @@ -72,12 +72,12 @@ class CocoaTcpConnection: ARAsyncConnection, GCDAsyncSocketDelegate { } // On connection closed - func socketDidDisconnect(_ sock: GCDAsyncSocket!, withError err: NSError!) { + func socketDidDisconnect(_ sock: GCDAsyncSocket!, withError err: Error?) { // NSLog("\(TAG) Connection closed...") onClosed() } - func socket(_ sock: GCDAsyncSocket!, didRead data: Data!, withTag tag: Int) { + func socket(_ sock: GCDAsyncSocket, didRead data: Data, withTag tag: Int) { if (tag == READ_HEADER) { // NSLog("\(TAG) Header received") self.header = data @@ -86,12 +86,12 @@ class CocoaTcpConnection: ARAsyncConnection, GCDAsyncSocketDelegate { gcdSocket?.readData(toLength: UInt(size + 4), withTimeout: -1, tag: READ_BODY) } else if (tag == READ_BODY) { // NSLog("\(TAG) Body received") - let package = NSMutableData() + var package = Data() package.append(self.header!) package.append(data) - // package.readUInt32(0) // IGNORE: package id + package.readUInt32(0) // IGNORE: package id self.header = nil - // onReceived(package.toJavaBytes()) + onReceived(package.toJavaBytes()) gcdSocket?.readData(toLength: UInt(9), withTimeout: -1, tag: READ_HEADER) } else { diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/Storage/FMDBBridge.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/Storage/FMDBBridge.swift index addc8b685e..1a9bcd574c 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/Storage/FMDBBridge.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/Storage/FMDBBridge.swift @@ -33,9 +33,7 @@ extension JavaLangLong { } extension Data { - func readNSData(_ offset: Int, len: Int) -> Data { - // return self.subdata(in: Int(offset)...Int(offset + len)) - return self + return self.subdata(in: self.startIndex.advanced(by: Int(offset)).. FMResultSet? { + func executeQuery(_ sql:String, _ values: Any...) -> FMResultSet? { return executeQuery(sql, withArgumentsIn: values); } @@ -27,7 +27,7 @@ extension FMDatabase { /// /// :returns: This returns true if successful. Returns false upon error. - func executeUpdate(_ sql:String, _ values: AnyObject...) -> Bool { + func executeUpdate(_ sql:String, _ values: Any...) -> Bool { return executeUpdate(sql, withArgumentsIn: values); } @@ -39,10 +39,10 @@ extension FMDatabase { /// /// :returns: This returns the T value if value is found. Returns nil if column is NULL or upon error. - fileprivate func valueForQuery(_ sql: String, values: NSArray?, completionHandler:(FMResultSet)->(T!)) -> T! { + fileprivate func valueForQuery(_ sql: String, values: Array?, completionHandler:(FMResultSet)->(T!)) -> T! { var result: T! - if let rs = executeQuery(sql, withArgumentsIn: values! as [AnyObject]) { + if let rs = executeQuery(sql, withArgumentsIn: values!) { if rs.next() { let obj = rs.object(forColumnIndex: 0) as! NSObject if !(obj is NSNull) { @@ -63,8 +63,8 @@ extension FMDatabase { /// /// :returns: This returns string value if value is found. Returns nil if column is NULL or upon error. - func stringForQuery(_ sql: String, _ values: AnyObject...) -> String! { - return valueForQuery(sql, values: values as NSArray) { $0.string(forColumnIndex: 0) } + func stringForQuery(_ sql: String, _ values: Any...) -> String! { + return valueForQuery(sql, values: values) { $0.string(forColumnIndex: 0) } } /// This is a rendition of intForQuery that handles Swift variadic parameters @@ -75,8 +75,8 @@ extension FMDatabase { /// /// :returns: This returns integer value if value is found. Returns nil if column is NULL or upon error. - func intForQuery(_ sql: String, _ values: AnyObject...) -> Int32! { - return valueForQuery(sql, values: values as NSArray) { $0.int(forColumnIndex: 0) } + func intForQuery(_ sql: String, _ values: Any...) -> Int32! { + return valueForQuery(sql, values: values) { $0.int(forColumnIndex: 0) } } /// This is a rendition of longForQuery that handles Swift variadic parameters @@ -87,8 +87,8 @@ extension FMDatabase { /// /// :returns: This returns long value if value is found. Returns nil if column is NULL or upon error. - func longForQuery(_ sql: String, _ values: AnyObject...) -> Int! { - return valueForQuery(sql, values: values as NSArray) { $0.long(forColumnIndex: 0) } + func longForQuery(_ sql: String, _ values: Any...) -> Int! { + return valueForQuery(sql, values: values) { $0.long(forColumnIndex: 0) } } /// This is a rendition of boolForQuery that handles Swift variadic parameters @@ -99,8 +99,8 @@ extension FMDatabase { /// /// :returns: This returns Bool value if value is found. Returns nil if column is NULL or upon error. - func boolForQuery(_ sql: String, _ values: AnyObject...) -> Bool! { - return valueForQuery(sql, values: values as NSArray) { $0.bool(forColumnIndex: 0) } + func boolForQuery(_ sql: String, _ values: Any...) -> Bool! { + return valueForQuery(sql, values: values) { $0.bool(forColumnIndex: 0) } } /// This is a rendition of doubleForQuery that handles Swift variadic parameters @@ -111,8 +111,8 @@ extension FMDatabase { /// /// :returns: This returns Double value if value is found. Returns nil if column is NULL or upon error. - func doubleForQuery(_ sql: String, _ values: AnyObject...) -> Double! { - return valueForQuery(sql, values: values as NSArray) { $0.double(forColumnIndex: 0) } + func doubleForQuery(_ sql: String, _ values: Any...) -> Double! { + return valueForQuery(sql, values: values) { $0.double(forColumnIndex: 0) } } /// This is a rendition of dateForQuery that handles Swift variadic parameters @@ -123,8 +123,8 @@ extension FMDatabase { /// /// :returns: This returns NSDate value if value is found. Returns nil if column is NULL or upon error. - func dateForQuery(_ sql: String, _ values: AnyObject...) -> Date! { - return valueForQuery(sql, values: values as NSArray) { $0.date(forColumnIndex: 0) } + func dateForQuery(_ sql: String, _ values: Any...) -> Date! { + return valueForQuery(sql, values: values) { $0.date(forColumnIndex: 0) } } /// This is a rendition of dataForQuery that handles Swift variadic parameters @@ -135,7 +135,7 @@ extension FMDatabase { /// /// :returns: This returns NSData value if value is found. Returns nil if column is NULL or upon error. - func dataForQuery(_ sql: String, _ values: AnyObject...) -> Data! { - return valueForQuery(sql, values: values as NSArray) { $0.data(forColumnIndex: 0) } + func dataForQuery(_ sql: String, _ values: Any...) -> Data! { + return valueForQuery(sql, values: values) { $0.data(forColumnIndex: 0) } } } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/Storage/FMDBKeyValue.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/Storage/FMDBKeyValue.swift index f278a99831..6fdd82d4fe 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/Storage/FMDBKeyValue.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/Storage/FMDBKeyValue.swift @@ -104,11 +104,11 @@ import Foundation func loadAllItems() -> JavaUtilList! { checkTable() - let res = JavaUtilArrayList() + let res = JavaUtilArrayList()! if let result = db.executeQuery(queryAll) { while(result.next()) { - res!.add(withId: ARKeyValueRecord(key: jlong(result.longLongInt(forColumn: "ID")), withData: result.data(forColumn: "BYTES").toJavaBytes())) + res.add(withId: ARKeyValueRecord(key: jlong(result.longLongInt(forColumn: "ID")), withData: result.data(forColumn: "BYTES").toJavaBytes())) } } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/Storage/FMDBList.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/Storage/FMDBList.swift index f85f52da49..43227ca25e 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/Storage/FMDBList.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/Storage/FMDBList.swift @@ -251,11 +251,11 @@ class FMDBList : NSObject, ARListStorageDisplayEx { checkTable(); var result : FMResultSet? = nil; -// if (sortingKey == nil) { -// result = db!.executeQuery(queryForwardFilterFirst, query + "%", "% " + query + "%", limit.toNSNumber()); -// } else { -// result = db!.executeQuery(queryForwardFilterMore, query + "%", "% " + query + "%", sortingKey!.toNSNumber(), limit.toNSNumber()); -// } + if (sortingKey == nil) { + result = db!.executeQuery(queryForwardFilterFirst, query + "%", "% " + query + "%", limit.toNSNumber()); + } else { + result = db!.executeQuery(queryForwardFilterMore, query + "%", "% " + query + "%", sortingKey!.toNSNumber(), limit.toNSNumber()); + } if (result == nil) { NSLog(db!.lastErrorMessage()) return nil @@ -308,11 +308,11 @@ class FMDBList : NSObject, ARListStorageDisplayEx { checkTable(); var result : FMResultSet? = nil; -// if (sortingKey == nil) { -// result = db!.executeQuery(queryBackwardFilterFirst, query + "%", "% " + query + "%", limit.toNSNumber()); -// } else { -// result = db!.executeQuery(queryBackwardFilterMore, query + "%", "% " + query + "%", sortingKey!.toNSNumber(), limit.toNSNumber()); -// } + if (sortingKey == nil) { + result = db!.executeQuery(queryBackwardFilterFirst, query + "%", "% " + query + "%", limit.toNSNumber()) + } else { + result = db!.executeQuery(queryBackwardFilterMore, query + "%", "% " + query + "%", sortingKey!.toNSNumber(), limit.toNSNumber()) + } if (result == nil) { NSLog(db!.lastErrorMessage()) return nil diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/iOSNotificationProvider.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/iOSNotificationProvider.swift index 9924656eae..ec1729a4c5 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/iOSNotificationProvider.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/iOSNotificationProvider.swift @@ -24,7 +24,7 @@ import AudioToolbox.AudioServices var path = Bundle.framework.url(forResource: "notification", withExtension: "caf") if let fileURL: URL = URL(fileURLWithPath: "/Library/Ringtones/\(soundFile)") { - path = fileURL + path = fileURL } AudioServicesCreateSystemSoundID(path! as CFURL, &internalMessage) diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Auth/AAAuthEmailViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Auth/AAAuthEmailViewController.swift index 054772ccf5..ce9dbfa99a 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Auth/AAAuthEmailViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Auth/AAAuthEmailViewController.swift @@ -84,7 +84,7 @@ open class AAAuthEmailViewController: AAAuthViewController { // Terms Of Service // if showTos { - let tosRange = NSRange(location: hintText.indexOf(tosText!)!, length: tosText!.length) + let tosRange = NSRange(location: hintText.indexOf(tosText)!, length: tosText.length) let tosLink = YYTextHighlight() tosLink.setColor(ActorSDK.sharedActor().style.authTintColor.alpha(0.56)) tosLink.tapAction = { (container, text, range, rect) in @@ -99,7 +99,7 @@ open class AAAuthEmailViewController: AAAuthViewController { // Privacy Policy // if showPrivacy { - let privacyRange = NSRange(location: hintText.indexOf(privacyText!)!, length: privacyText!.length) + let privacyRange = NSRange(location: hintText.indexOf(privacyText)!, length: privacyText.length) let privacyLink = YYTextHighlight() privacyLink.setColor(ActorSDK.sharedActor().style.authTintColor.alpha(0.56)) privacyLink.tapAction = { (container, text, range, rect) in diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Auth/AAAuthLogInViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Auth/AAAuthLogInViewController.swift index 3188ec2c21..0f9174ed45 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Auth/AAAuthLogInViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Auth/AAAuthLogInViewController.swift @@ -17,7 +17,7 @@ open class AAAuthLogInViewController: AAAuthViewController { public override init() { super.init(nibName: nil, bundle: nil) -// navigationItem.leftBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationCancel"), style: .plain, target: self, action: #selector(AAViewController.dismiss)) + navigationItem.leftBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationCancel"), style: .plain, target: self, action: #selector(AAViewController.dismissController)) } public required init(coder aDecoder: NSCoder) { diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Auth/AAAuthNameViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Auth/AAAuthNameViewController.swift index 8eeec0803d..7943690112 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Auth/AAAuthNameViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Auth/AAAuthNameViewController.swift @@ -20,7 +20,7 @@ open class AAAuthNameViewController: AAAuthViewController { self.transactionHash = transactionHash super.init(nibName: nil, bundle: nil) -// navigationItem.leftBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationCancel"), style: .plain, target: self, action: #selector(AAViewController.dismiss)) + navigationItem.leftBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationCancel"), style: .plain, target: self, action: #selector(AAViewController.dismissController)) } public required init(coder aDecoder: NSCoder) { diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Auth/AAAuthPhoneViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Auth/AAAuthPhoneViewController.swift index 94208bbd80..dd546ada07 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Auth/AAAuthPhoneViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Auth/AAAuthPhoneViewController.swift @@ -102,13 +102,13 @@ class AAAuthPhoneViewController: AAAuthViewController, AACountryViewControllerDe // if showTos { let tosLink = YYTextHighlight() - let tosRange = NSRange(location: hintText.indexOf(tosText!)!, length: tosText!.length) + let tosRange = NSRange(location: hintText.indexOf(tosText)!, length: tosText.length) tosLink.setColor(ActorSDK.sharedActor().style.authTintColor.alpha(0.56)) tosLink.tapAction = { (container, text, range, rect) in if let url = ActorSDK.sharedActor().termsOfServiceUrl { self.openUrl(url) } else if let text = ActorSDK.sharedActor().termsOfServiceText { - self.present(AABigAlertController(alertTitle: tosText!, alertMessage: text), animated: true, completion: nil) + self.present(AABigAlertController(alertTitle: tosText, alertMessage: text), animated: true, completion: nil) } } attributedTerms.yy_setColor(ActorSDK.sharedActor().style.authTintColor, range: tosRange) @@ -121,13 +121,13 @@ class AAAuthPhoneViewController: AAAuthViewController, AACountryViewControllerDe // if showPrivacy { let privacyLink = YYTextHighlight() - let privacyRange = NSRange(location: hintText.indexOf(privacyText!)!, length: privacyText!.length) + let privacyRange = NSRange(location: hintText.indexOf(privacyText)!, length: privacyText.length) privacyLink.setColor(ActorSDK.sharedActor().style.authTintColor.alpha(0.56)) privacyLink.tapAction = { (container, text, range, rect) in if let url = ActorSDK.sharedActor().privacyPolicyUrl { self.openUrl(url) } else if let text = ActorSDK.sharedActor().privacyPolicyText { - self.present(AABigAlertController(alertTitle: privacyText!, alertMessage: text), animated: true, completion: nil) + self.present(AABigAlertController(alertTitle: privacyText, alertMessage: text), animated: true, completion: nil) } } attributedTerms.yy_setColor(ActorSDK.sharedActor().style.authTintColor, range: privacyRange) diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Auth/AACountryViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Auth/AACountryViewController.swift index e638416ecc..09cbeae540 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Auth/AACountryViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Auth/AACountryViewController.swift @@ -20,7 +20,7 @@ open class AACountryViewController: AATableViewController { self.title = AALocalized("AuthCountryTitle") - let cancelButtonItem = UIBarButtonItem(title: AALocalized("NavigationCancel"), style: UIBarButtonItemStyle.plain, target: self, action: Selector("dismiss")) + let cancelButtonItem = UIBarButtonItem(title: AALocalized("NavigationCancel"), style: UIBarButtonItemStyle.plain, target: self, action: #selector(AAViewController.dismissController)) self.navigationItem.setLeftBarButton(cancelButtonItem, animated: false) self.content = ACAllEvents_Auth.auth_PICK_COUNTRY() @@ -58,9 +58,11 @@ open class AACountryViewController: AATableViewController { } fileprivate func letters() -> Array { -// if (_letters == nil) { -// _letters = (countries().allKeys as Array).sortedArray(using: #selector(YYTextPosition.compare(_:))) -// } + if (_letters == nil) { + _letters = (countries().allKeys as! [String]).sorted(by: { (a: String, b: String) -> Bool in + return a < b + }) + } return _letters } @@ -77,12 +79,14 @@ open class AACountryViewController: AATableViewController { } open override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return (countries()[letters()[section] as! String] as! NSArray).count + let letter = letters()[section] + let cs = countries().object(forKey: letter) as! NSArray + return cs.count } open override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell: AAAuthCountryCell = tableView.dequeueCell(indexPath) - let letter = letters()[(indexPath as NSIndexPath).section] as! String + let letter = letters()[(indexPath as NSIndexPath).section] let countryData = (countries().object(forKey: letter) as! NSArray)[(indexPath as NSIndexPath).row] as! [String] cell.setTitle(countryData[0]) @@ -95,12 +99,12 @@ open class AACountryViewController: AATableViewController { open func tableView(_ tableView: UITableView, didSelectRowAtIndexPath indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) - let letter = letters()[(indexPath as NSIndexPath).section] as! String + let letter = letters()[(indexPath as NSIndexPath).section] let countryData = (countries().object(forKey: letter) as! NSArray)[(indexPath as NSIndexPath).row] as! [String] delegate?.countriesController(self, didChangeCurrentIso: countryData[1]) - dismiss() + dismissController() } open func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { @@ -108,7 +112,7 @@ open class AACountryViewController: AATableViewController { } open func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { - return letters()[section] as? String + return letters()[section] } open override func viewWillDisappear(_ animated: Bool) { diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Calls/AACallViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Calls/AACallViewController.swift index 97225246fe..444ed1e7a3 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Calls/AACallViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Calls/AACallViewController.swift @@ -325,7 +325,7 @@ open class AACallViewController: AAViewController, RTCEAGLVideoViewDelegate { if (!self.isScheduledDispose) { self.isScheduledDispose = true dispatchAfterOnUi(0.8) { - self.dismiss() + self.dismissController() } } } else { diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Compose/AAComposeController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Compose/AAComposeController.swift index 1814aa08cd..f3be4ee748 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Compose/AAComposeController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Compose/AAComposeController.swift @@ -15,7 +15,7 @@ open class AAComposeController: AAContactsListContentController, AAContactsListC self.navigationItem.title = AALocalized("ComposeTitle") if AADevice.isiPad { -// self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationCancel"), style: UIBarButtonItemStyle.plain, target: self, action: #selector(AAViewController.dismiss)) + self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationCancel"), style: UIBarButtonItemStyle.plain, target: self, action: #selector(self.dismissController)) } } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Compose/AAGroupCreateViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Compose/AAGroupCreateViewController.swift index be5d9402cb..ddfc337937 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Compose/AAGroupCreateViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Compose/AAGroupCreateViewController.swift @@ -25,7 +25,7 @@ open class AAGroupCreateViewController: AAViewController, UITextFieldDelegate { self.navigationItem.title = AALocalized("CreateGroupTitle") } if AADevice.isiPad { -// self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationCancel"), style: UIBarButtonItemStyle.plain, target: self, action: #selector(AAViewController.dismiss)) + self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationCancel"), style: UIBarButtonItemStyle.plain, target: self, action: #selector(self.dismissController)) } self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationNext"), style: UIBarButtonItemStyle.done, target: self, action: #selector(AAGroupCreateViewController.doNext)) } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Compose/AAGroupMembersController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Compose/AAGroupMembersController.swift index 9b0e44d3c5..0aa96b50ab 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Compose/AAGroupMembersController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Compose/AAGroupMembersController.swift @@ -25,7 +25,7 @@ open class GroupMembersController: AAContactsListContentController, AAContactsLi navigationItem.title = AALocalized("CreateGroupMembersTitle") if AADevice.isiPad { -// self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationCancel"), style: UIBarButtonItemStyle.plain, target: self, action: #selector(self.dismiss)) + self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationCancel"), style: UIBarButtonItemStyle.plain, target: self, action: #selector(self.dismissController)) } navigationItem.rightBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationDone"), style: UIBarButtonItemStyle.done, target: self, action: #selector(GroupMembersController.doNext)) @@ -45,7 +45,7 @@ open class GroupMembersController: AAContactsListContentController, AAContactsLi tokenView.tintColor = appStyle.vcTokenTintColor tokenView.fieldName = "" - let placeholder = AALocalized("CreateGroupMembersPlaceholders")! + let placeholder = AALocalized("CreateGroupMembersPlaceholders") let attributedPlaceholder = NSMutableAttributedString(string: placeholder) attributedPlaceholder.addAttribute(NSForegroundColorAttributeName, value: appStyle.vcHintColor, range: NSRange(location: 0, length: placeholder.length)) tokenView.placeholderAttributedText = attributedPlaceholder @@ -91,7 +91,7 @@ open class GroupMembersController: AAContactsListContentController, AAContactsLi } else { self.navigateDetail(ConversationViewController(peer: ACPeer.group(with: gid))) } - self.dismiss() + self.dismissController() } } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Contacts/AAContactsViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Contacts/AAContactsViewController.swift index effbef031a..c4a4b50730 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Contacts/AAContactsViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Contacts/AAContactsViewController.swift @@ -184,14 +184,14 @@ open class AAContactsViewController: AAContactsListContentController, AAContacts } else { self.navigateDetail(ConversationViewController(peer: ACPeer_userWithInt_(user!.getId()))) } - c.dismiss() + c.dismissController() }, failureBlock: { (val) -> Void in if let customController = ActorSDK.sharedActor().delegate.actorControllerForConversation(ACPeer_userWithInt_(user!.getId())) { self.navigateDetail(customController) } else { self.navigateDetail(ConversationViewController(peer: ACPeer_userWithInt_(user!.getId()))) } - c.dismiss() + c.dismissController() }) } else { c.alertUser("FindNotFound") diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Input/AAEditFieldController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Input/AAEditFieldController.swift index 14e5db5675..d2b1e471c9 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Input/AAEditFieldController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Input/AAEditFieldController.swift @@ -90,7 +90,7 @@ open class AAEditFieldController: AAContentTableController { if config.didDismissTap != nil { config.didDismissTap!(self) } else { - dismiss() + dismissController() } } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Input/AAEditTextController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Input/AAEditTextController.swift index b10839a32b..43b8bc6c4b 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Input/AAEditTextController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Input/AAEditTextController.swift @@ -88,7 +88,7 @@ open class AAEditTextController: AAViewController { if self.config.didDismissTap != nil { self.config.didDismissTap!(self) } else { - dismiss() + dismissController() } } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/WebActions/AAWebActionController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/WebActions/AAWebActionController.swift index faf0ebef25..32c2244f47 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/WebActions/AAWebActionController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/WebActions/AAWebActionController.swift @@ -44,7 +44,7 @@ open class AAWebActionController: AAViewController, UIWebViewDelegate { // Match end url if regex.test(rawUrl) { self.executeSafe(Actor.completeWebAction(withHash: desc.getActionHash(), withUrl: rawUrl)) { (val) -> Void in - self.dismiss() + self.dismissController() } return false } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAAddParticipantViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAAddParticipantViewController.swift index 550868c235..ec1d12a89f 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAAddParticipantViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAAddParticipantViewController.swift @@ -51,7 +51,7 @@ open class AAAddParticipantViewController: AAContactsListContentController, AACo if !isAlreadyMember(contact.uid) { self.executeSafeOnlySuccess(Actor.inviteMemberCommand(withGid: jint(gid), withUid: jint(contact.uid))) { (val) -> () in - self.dismiss() + self.dismissController() } } return true diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupEditInfoViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupEditInfoViewController.swift index 53ed6c0ac0..791f0b10a4 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupEditInfoViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupEditInfoViewController.swift @@ -150,7 +150,7 @@ open class AAGroupEditInfoController: AAViewController, UITextViewDelegate { func cancelEdit() { nameInput.resignFirstResponder() descriptionView.resignFirstResponder() - dismiss() + dismissController() } open func textViewDidChange(_ textView: UITextView) { diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupTypeController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupTypeController.swift index 2f5734d454..2b8a7285f9 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupTypeController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Group/AAGroupTypeController.swift @@ -163,7 +163,7 @@ open class AAGroupTypeViewController: AAContentTableController { } else { self.navigateDetail(ConversationViewController(peer: ACPeer.group(with: jint(self.gid)))) } - self.dismiss() + self.dismissController() } else { self.navigateBack() } @@ -175,7 +175,7 @@ open class AAGroupTypeViewController: AAContentTableController { } else { self.navigateDetail(ConversationViewController(peer: ACPeer.group(with: jint(self.gid)))) } - self.dismiss() + self.dismissController() } else { navigateBack() } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/AAViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/AAViewController.swift index 8580069fde..d82c183b8e 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/AAViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/AAViewController.swift @@ -166,7 +166,7 @@ open class AAViewController: UIViewController, UINavigationControllerDelegate, U } } - open func dismiss() { + open func dismissController() { self.dismiss(animated: true, completion: nil) } @@ -235,8 +235,9 @@ open class AAViewController: UIViewController, UINavigationControllerDelegate, U controller.modalPresentationStyle = .formSheet present(controller, animated: true, completion: nil) } else { - controller.modalPresentationStyle = .custom - controller.transitioningDelegate = self + // controller.modalPresentationStyle = .custom + // controller.modalPresentationStyle = .custom + // controller.transitioningDelegate = self present(controller, animated: true, completion: nil) } } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/Alerts.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/Alerts.swift index e96d685a9f..c75fa6a442 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/Alerts.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/Alerts.swift @@ -60,13 +60,13 @@ public extension UIViewController { let controller = UIAlertController(title: title, message: nil, preferredStyle: UIAlertControllerStyle.actionSheet) if cancelButton != nil { - controller.addAction(UIAlertAction(title: AALocalized(cancelButton), style: UIAlertActionStyle.cancel, handler: { (alertView) -> () in + controller.addAction(UIAlertAction(title: AALocalized(cancelButton!), style: UIAlertActionStyle.cancel, handler: { (alertView) -> () in tapClosure(-1) })) } if destructButton != nil { - controller.addAction(UIAlertAction(title: AALocalized(destructButton), style: UIAlertActionStyle.destructive, handler: { (alertView) -> () in + controller.addAction(UIAlertAction(title: AALocalized(destructButton!), style: UIAlertActionStyle.destructive, handler: { (alertView) -> () in tapClosure(-2) })) } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/ManagedCells.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/ManagedCells.swift index f0f1e8c1a8..c96c03b6d3 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/ManagedCells.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Managed Runtime/ManagedCells.swift @@ -233,7 +233,9 @@ public extension AAManagedSection { public func text(_ title: String?, closure: (_ r: AATextRow) -> ()) -> AATextRow { let r = text() - r.title = AALocalized(title) + if title != nil { + r.title = AALocalized(title!) + } closure(r) r.bindAction?(r) r.initTable(self.table) @@ -242,7 +244,9 @@ public extension AAManagedSection { public func text(_ title: String?, content: String) -> AATextRow { let r = text() - r.title = AALocalized(title) + if title != nil { + r.title = AALocalized(title!) + } r.content = content r.initTable(self.table) return r diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Settings/AASettingsViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Settings/AASettingsViewController.swift index fcdfb1409f..e853061b1b 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Settings/AASettingsViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Settings/AASettingsViewController.swift @@ -132,7 +132,7 @@ open class AASettingsViewController: AAContentTableController { } c.executeSafeOnlySuccess(Actor.editMyNameCommand(withName: t)!) { (val) -> Void in - c.dismiss() + c.dismissController() } } } @@ -220,7 +220,7 @@ open class AASettingsViewController: AAContentTableController { nNick = nil } c.executeSafeOnlySuccess(Actor.editMyNickCommand(withNick: nNick)!, successBlock: { (val) -> Void in - c.dismiss() + c.dismissController() }) } } @@ -261,7 +261,7 @@ open class AASettingsViewController: AAContentTableController { updatedText = nil } controller.executeSafeOnlySuccess(Actor.editMyAboutCommand(withNick: updatedText)!, successBlock: { (val) -> Void in - controller.dismiss() + controller.dismissController() }) } } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/User/AAUserViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/User/AAUserViewController.swift index 2f72a2b708..f84797eb52 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/User/AAUserViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/User/AAUserViewController.swift @@ -237,7 +237,7 @@ class AAUserViewController: AAContentTableController { return } c.executeSafeOnlySuccess(Actor.editNameCommand(withUid: jint(self.uid), withName: d)!, successBlock: { (val) -> Void in - c.dismiss() + c.dismissController() }) } } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/SwiftExtensions/AALocalized.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/SwiftExtensions/AALocalized.swift index 6cd41df358..2402359ba8 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/SwiftExtensions/AALocalized.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/SwiftExtensions/AALocalized.swift @@ -6,10 +6,7 @@ import Foundation // Shorter helper for localized strings -public func AALocalized(_ text: String!) -> String! { - if text == nil { - return nil - } +public func AALocalized(_ text: String) -> String { let appRes = NSLocalizedString(text, comment: "") diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/AAAvatarView.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/AAAvatarView.swift index db276c1a92..a1b349b4e3 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/AAAvatarView.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Views/AAAvatarView.swift @@ -147,7 +147,7 @@ open class AAAvatarView: UIView, YYAsyncLayerDelegate, ACFileEventCallback { context.setFillColor(color) - // context.addArc(r, r, r, CGFloat(M_PI * 0), CGFloat(M_PI * 2), 0) + context.addEllipse(in: CGRect(x: 0, y: 0, width: r * 2, height: r * 2)) if isCancelled() { return @@ -206,7 +206,7 @@ open class AAAvatarView: UIView, YYAsyncLayerDelegate, ACFileEventCallback { // Clean BG context.setFillColor(UIColor.white.cgColor) - // context.addArc(r, r, r, CGFloat(M_PI * 0), CGFloat(M_PI * 2), 0) + context.addEllipse(in: CGRect(x: 0, y: 0, width: r * 2, height: r * 2)) if isCancelled() { return @@ -226,7 +226,7 @@ open class AAAvatarView: UIView, YYAsyncLayerDelegate, ACFileEventCallback { return } - // context.addArc(r, r, r, CGFloat(M_PI * 0), CGFloat(M_PI * 2), 0) + context.addEllipse(in: CGRect(x: 0, y: 0, width: r * 2, height: r * 2)) if isCancelled() { return @@ -247,7 +247,7 @@ open class AAAvatarView: UIView, YYAsyncLayerDelegate, ACFileEventCallback { return } - // context.addArc(r, r, r, CGFloat(M_PI * 0), CGFloat(M_PI * 2), 0) + context.addEllipse(in: CGRect(x: 0, y: 0, width: r * 2, height: r * 2)) if isCancelled() { return From 3893f162cfafbd0fc40cc016e36eb72eb6b9b4e5 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Fri, 23 Sep 2016 04:29:02 +0300 Subject: [PATCH 366/414] fix(iOS): Fixing dependency in podspec --- actor-sdk/sdk-core-ios/ActorSDK-iOS.podspec | 1 + 1 file changed, 1 insertion(+) diff --git a/actor-sdk/sdk-core-ios/ActorSDK-iOS.podspec b/actor-sdk/sdk-core-ios/ActorSDK-iOS.podspec index 9a4fe8b85c..a014dbfb08 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK-iOS.podspec +++ b/actor-sdk/sdk-core-ios/ActorSDK-iOS.podspec @@ -18,6 +18,7 @@ Pod::Spec.new do |s| s.dependency 'RegexKitLite' s.dependency 'zipzap' s.dependency 'J2ObjC-Framework' + s.dependency 'Reachability' # UI s.dependency 'VBFPopFlatButton' From 2f95c93243235a56167ef5ce06f4e03333c1e928 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Fri, 23 Sep 2016 04:31:33 +0300 Subject: [PATCH 367/414] fix(iOS): Fixing dependency in podspec --- actor-sdk/sdk-core-ios/ActorSDK-iOS.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actor-sdk/sdk-core-ios/ActorSDK-iOS.podspec b/actor-sdk/sdk-core-ios/ActorSDK-iOS.podspec index a014dbfb08..b0b461e8eb 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK-iOS.podspec +++ b/actor-sdk/sdk-core-ios/ActorSDK-iOS.podspec @@ -18,7 +18,7 @@ Pod::Spec.new do |s| s.dependency 'RegexKitLite' s.dependency 'zipzap' s.dependency 'J2ObjC-Framework' - s.dependency 'Reachability' + s.dependency 'ReachabilitySwift' # UI s.dependency 'VBFPopFlatButton' From 659ddd8dea65f77acd7aaf2ead08a082b3ffe606 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Fri, 23 Sep 2016 13:06:14 +0300 Subject: [PATCH 368/414] fix(android): profile fragment avatar placeholder text size, hide call/videoCall dividers --- .../android-app/src/main/java/im/actor/Application.java | 4 +++- .../im/actor/sdk/controllers/profile/ProfileFragment.java | 6 +++++- .../android-sdk/src/main/res/layout/fragment_profile.xml | 3 +++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/Application.java b/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/Application.java index f20f359837..9722be5652 100644 --- a/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/Application.java +++ b/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/Application.java @@ -110,7 +110,9 @@ public void bindRawText(CharSequence rawText, long readDate, long receiveDate, S text.append("\n\n" + message.getSortDate()); } })); -// layouters.add(0, new DefaultLayouter(DefaultLayouter.TEXT_HOLDER, CensoredTextHolderEx::new)); + + layouters.add(0, new DefaultLayouter(DefaultLayouter.TEXT_HOLDER, CensoredTextHolderEx::new)); + layouters.add(0, new XmlBubbleLayouter(content -> content instanceof PhotoContent, R.layout.adapter_dialog_photo, (adapter1, root1, peer1) -> new PhotoHolder(adapter1, root1, peer1) { @Override protected void onConfigureViewHolder() { diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/profile/ProfileFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/profile/ProfileFragment.java index 3efae67e6f..bf969b4827 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/profile/ProfileFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/profile/ProfileFragment.java @@ -115,7 +115,7 @@ public View onCreateView(final LayoutInflater inflater, ViewGroup container, Bun // avatarView = (AvatarView) res.findViewById(R.id.avatar); - avatarView.init(Screen.dp(96), 44); + avatarView.init(Screen.dp(48), 22); avatarView.bind(user.getAvatar().get(), user.getName().get(), user.getId()); avatarView.setOnClickListener(v -> { startActivity(ViewAvatarActivity.viewAvatar(user.getId(), getActivity())); @@ -207,6 +207,7 @@ public View onCreateView(final LayoutInflater inflater, ViewGroup container, Bun // Voice Call // + View voiceCallDivider = res.findViewById(R.id.voiceCallDivider); View voiceCallView = res.findViewById(R.id.voiceCall); if (ActorSDK.sharedActor().isCallsEnabled() && !user.isBot()) { ImageView voiceViewIcon = (ImageView) voiceCallView.findViewById(R.id.actionIcon); @@ -221,6 +222,7 @@ public View onCreateView(final LayoutInflater inflater, ViewGroup container, Bun }); } else { voiceCallView.setVisibility(View.GONE); + voiceCallDivider.setVisibility(View.GONE); } // @@ -228,6 +230,7 @@ public View onCreateView(final LayoutInflater inflater, ViewGroup container, Bun // + View videoCallDivider = res.findViewById(R.id.videoCallDivider); View videoCallView = res.findViewById(R.id.videoCall); if (ActorSDK.sharedActor().isCallsEnabled() && !user.isBot()) { ImageView voiceViewIcon = (ImageView) videoCallView.findViewById(R.id.videoCallIcon); @@ -242,6 +245,7 @@ public View onCreateView(final LayoutInflater inflater, ViewGroup container, Bun }); } else { videoCallView.setVisibility(View.GONE); + videoCallDivider.setVisibility(View.GONE); } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_profile.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_profile.xml index 8da0e0727d..8ffa7e6ca8 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_profile.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_profile.xml @@ -71,6 +71,7 @@ android:orientation="vertical" /> Date: Fri, 23 Sep 2016 16:13:15 +0300 Subject: [PATCH 369/414] fix(server:groups): allow to block users in groups via personal blacklist of owner --- .../im/actor/server/dialog/UserAcl.scala | 14 +- .../im/actor/server/group/GroupErrors.scala | 2 + .../server/group/MemberCommandHandlers.scala | 654 +++++++++--------- .../rpc/service/groups/GroupRpcErrors.scala | 4 +- .../service/groups/GroupsServiceImpl.scala | 1 + 5 files changed, 349 insertions(+), 326 deletions(-) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/dialog/UserAcl.scala b/actor-server/actor-core/src/main/scala/im/actor/server/dialog/UserAcl.scala index a41b746404..1127e2f0ac 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/dialog/UserAcl.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/dialog/UserAcl.scala @@ -11,6 +11,7 @@ trait UserAcl { protected val system: ActorSystem + // TODO: clarify names of params. protected def withNonBlockedPeer[A]( contactUserId: Int, peer: Peer @@ -23,16 +24,17 @@ trait UserAcl { } protected def withNonBlockedUser[A]( - contactUserId: Int, - contactOwnerUserId: Int + userId: Int, + ownerUserId: Int )(default: ⇒ Future[A], failed: ⇒ Future[A]): Future[A] = { import system.dispatcher for { - isBlocked ← checkIsBlocked(contactUserId, contactOwnerUserId) + isBlocked ← checkIsBlocked(userId, ownerUserId) result ← if (isBlocked) failed else default } yield result } - protected def checkIsBlocked(contactUserId: Int, contactOwnerUserId: Int): Future[Boolean] = - DbExtension(system).db.run(RelationRepo.isBlocked(contactOwnerUserId, contactUserId)) -} \ No newline at end of file + // check that `userId` is blocked by `ownerUserId` + protected def checkIsBlocked(userId: Int, ownerUserId: Int): Future[Boolean] = + DbExtension(system).db.run(RelationRepo.isBlocked(ownerUserId, userId)) +} diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupErrors.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupErrors.scala index 5080d09a9b..578a11ec66 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupErrors.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupErrors.scala @@ -39,6 +39,8 @@ object GroupErrors { case object BlockedByUser extends Exception with NoStackTrace + case object UserIsBanned extends Exception with NoStackTrace + case object NoPermission extends Exception with NoStackTrace case object CantLeaveGroup extends Exception with NoStackTrace diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/MemberCommandHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/MemberCommandHandlers.scala index 22eef90f4b..94f068079c 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/MemberCommandHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/MemberCommandHandlers.scala @@ -32,173 +32,181 @@ private[group] trait MemberCommandHandlers extends GroupsImplicits { } else if (state.isMember(cmd.inviteeUserId)) { sender() ! Status.Failure(GroupErrors.UserAlreadyJoined) } else { - val inviteeIsExUser = state.isExUser(cmd.inviteeUserId) + val isBlockedFu = checkIsBlocked(cmd.inviteeUserId, state.ownerUserId) - persist(UserInvited(Instant.now, cmd.inviteeUserId, cmd.inviterUserId)) { evt ⇒ - val newState = commit(evt) + onSuccess(isBlockedFu) { isBlocked ⇒ + if (isBlocked) { + sender() ! Status.Failure(GroupErrors.UserIsBanned) + } else { + val inviteeIsExUser = state.isExUser(cmd.inviteeUserId) - val dateMillis = evt.ts.toEpochMilli - val memberIds = newState.memberIds + persist(UserInvited(Instant.now, cmd.inviteeUserId, cmd.inviterUserId)) { evt ⇒ + val newState = commit(evt) - // TODO: unify isHistoryShared usage - val inviteeUpdatesNew: Vector[Update] = { - val optDrop = if (newState.isHistoryShared) Some(UpdateChatDropCache(apiGroupPeer)) else None - optDrop ++: refreshGroupUpdates(newState, cmd.inviteeUserId) - } - - // For groups with not async members we should push Diff for members, and all Members for invitee - // For groups with async members we should push UpdateGroupMembersCountChanged for both invitee and members - val (inviteeUpdateNew, membersUpdateNew): (Update, Update) = - if (newState.isAsyncMembers) { - val u = UpdateGroupMembersCountChanged(groupId, newState.membersCount) - (u, u) - } else { - val apiMembers = newState.members.values.map(_.asStruct).toVector - val inviteeMember = apiMembers.find(_.userId == cmd.inviteeUserId) - - ( - UpdateGroupMembersUpdated(groupId, apiMembers), - UpdateGroupMemberDiff( - groupId, - addedMembers = inviteeMember.toVector, - membersCount = newState.membersCount, - removedUsers = Vector.empty - ) - ) - } - - val inviteeUpdateObsolete = UpdateGroupInviteObsolete( - groupId, - inviteUserId = cmd.inviterUserId, - date = dateMillis, - randomId = cmd.randomId - ) - - val membersUpdateObsolete = UpdateGroupUserInvitedObsolete( - groupId, - userId = cmd.inviteeUserId, - inviterUserId = cmd.inviterUserId, - date = dateMillis, - randomId = cmd.randomId - ) - val serviceMessage = GroupServiceMessages.userInvited(cmd.inviteeUserId) - - //TODO: remove deprecated - db.run(GroupUserRepo.create(groupId, cmd.inviteeUserId, cmd.inviterUserId, evt.ts, None, isAdmin = false): @silent) - - def inviteGROUPUpdates: Future[SeqStateDate] = - for { - // push updated members list/count to inviteeUserId, - // make it `FatSeqUpdate` if this user invited to group for first time. - _ ← seqUpdExt.deliverUserUpdate( - userId = cmd.inviteeUserId, - update = inviteeUpdateNew, - pushRules = seqUpdExt.pushRules(isFat = !inviteeIsExUser, Some(PushTexts.invited(newState.groupType))), - deliveryId = s"invite_${groupId}_${cmd.randomId}" - ) + val dateMillis = evt.ts.toEpochMilli + val memberIds = newState.memberIds - // push all "refresh group" updates to inviteeUserId - _ ← FutureExt.ftraverse(inviteeUpdatesNew) { update ⇒ - seqUpdExt.deliverUserUpdate(userId = cmd.inviteeUserId, update) + // TODO: unify isHistoryShared usage + val inviteeUpdatesNew: Vector[Update] = { + val optDrop = if (newState.isHistoryShared) Some(UpdateChatDropCache(apiGroupPeer)) else None + optDrop ++: refreshGroupUpdates(newState, cmd.inviteeUserId) } - // push updated members difference to all group members except inviteeUserId - SeqState(seq, state) ← seqUpdExt.broadcastClientUpdate( - userId = cmd.inviterUserId, - authId = cmd.inviterAuthId, - bcastUserIds = (memberIds - cmd.inviterUserId) - cmd.inviteeUserId, - update = membersUpdateNew, - deliveryId = s"useradded_${groupId}_${cmd.randomId}" - ) + // For groups with not async members we should push Diff for members, and all Members for invitee + // For groups with async members we should push UpdateGroupMembersCountChanged for both invitee and members + val (inviteeUpdateNew, membersUpdateNew): (Update, Update) = + if (newState.isAsyncMembers) { + val u = UpdateGroupMembersCountChanged(groupId, newState.membersCount) + (u, u) + } else { + val apiMembers = newState.members.values.map(_.asStruct).toVector + val inviteeMember = apiMembers.find(_.userId == cmd.inviteeUserId) + + ( + UpdateGroupMembersUpdated(groupId, apiMembers), + UpdateGroupMemberDiff( + groupId, + addedMembers = inviteeMember.toVector, + membersCount = newState.membersCount, + removedUsers = Vector.empty + ) + ) + } - // explicitly send service message - SeqStateDate(_, _, date) ← dialogExt.sendServerMessage( - apiGroupPeer, - cmd.inviterUserId, - cmd.inviterAuthId, - cmd.randomId, - serviceMessage, - deliveryTag = Some(Optimization.GroupV2) + val inviteeUpdateObsolete = UpdateGroupInviteObsolete( + groupId, + inviteUserId = cmd.inviterUserId, + date = dateMillis, + randomId = cmd.randomId ) - } yield SeqStateDate(seq, state, date) - def inviteCHANNELUpdates: Future[SeqStateDate] = - for { - // push updated members count to inviteeUserId - _ ← seqUpdExt.deliverUserUpdate( + val membersUpdateObsolete = UpdateGroupUserInvitedObsolete( + groupId, userId = cmd.inviteeUserId, - update = inviteeUpdateNew, - pushRules = seqUpdExt.pushRules(isFat = false, Some(PushTexts.invited(newState.groupType))), - deliveryId = s"invite_${groupId}_${cmd.randomId}" + inviterUserId = cmd.inviterUserId, + date = dateMillis, + randomId = cmd.randomId ) + val serviceMessage = GroupServiceMessages.userInvited(cmd.inviteeUserId) - // push all "refresh group" updates to inviteeUserId - _ ← FutureExt.ftraverse(inviteeUpdatesNew) { update ⇒ - seqUpdExt.deliverUserUpdate(userId = cmd.inviteeUserId, update) - } + //TODO: remove deprecated + db.run(GroupUserRepo.create(groupId, cmd.inviteeUserId, cmd.inviterUserId, evt.ts, None, isAdmin = false): @silent) - // push updated members count to all group members - SeqState(seq, state) ← seqUpdExt.broadcastClientUpdate( - userId = cmd.inviterUserId, - authId = cmd.inviterAuthId, - bcastUserIds = (memberIds - cmd.inviterUserId) - cmd.inviteeUserId, - update = membersUpdateNew, - deliveryId = s"useradded_${groupId}_${cmd.randomId}" - ) + def inviteGROUPUpdates: Future[SeqStateDate] = + for { + // push updated members list/count to inviteeUserId, + // make it `FatSeqUpdate` if this user invited to group for first time. + _ ← seqUpdExt.deliverUserUpdate( + userId = cmd.inviteeUserId, + update = inviteeUpdateNew, + pushRules = seqUpdExt.pushRules(isFat = !inviteeIsExUser, Some(PushTexts.invited(newState.groupType))), + deliveryId = s"invite_${groupId}_${cmd.randomId}" + ) - // push service message to invitee - _ ← seqUpdExt.deliverUserUpdate( - userId = cmd.inviteeUserId, - update = serviceMessageUpdate( - cmd.inviterUserId, - dateMillis, - cmd.randomId, - serviceMessage - ), - deliveryTag = Some(Optimization.GroupV2) - ) - _ ← dialogExt.bump(cmd.inviteeUserId, apiGroupPeer.asModel) - } yield SeqStateDate(seq, state, dateMillis) + // push all "refresh group" updates to inviteeUserId + _ ← FutureExt.ftraverse(inviteeUpdatesNew) { update ⇒ + seqUpdExt.deliverUserUpdate(userId = cmd.inviteeUserId, update) + } + + // push updated members difference to all group members except inviteeUserId + SeqState(seq, state) ← seqUpdExt.broadcastClientUpdate( + userId = cmd.inviterUserId, + authId = cmd.inviterAuthId, + bcastUserIds = (memberIds - cmd.inviterUserId) - cmd.inviteeUserId, + update = membersUpdateNew, + deliveryId = s"useradded_${groupId}_${cmd.randomId}" + ) - val result: Future[SeqStateDate] = for { - /////////////////////////// - // Groups V1 API updates // - /////////////////////////// + // explicitly send service message + SeqStateDate(_, _, date) ← dialogExt.sendServerMessage( + apiGroupPeer, + cmd.inviterUserId, + cmd.inviterAuthId, + cmd.randomId, + serviceMessage, + deliveryTag = Some(Optimization.GroupV2) + ) + } yield SeqStateDate(seq, state, date) - // push "Invited" to invitee - _ ← seqUpdExt.deliverUserUpdate( - userId = cmd.inviteeUserId, - inviteeUpdateObsolete, - pushRules = seqUpdExt.pushRules(isFat = true, Some(PushTexts.invited(newState.groupType))), - deliveryId = s"invite_obsolete_${groupId}_${cmd.randomId}" - ) + def inviteCHANNELUpdates: Future[SeqStateDate] = + for { + // push updated members count to inviteeUserId + _ ← seqUpdExt.deliverUserUpdate( + userId = cmd.inviteeUserId, + update = inviteeUpdateNew, + pushRules = seqUpdExt.pushRules(isFat = false, Some(PushTexts.invited(newState.groupType))), + deliveryId = s"invite_${groupId}_${cmd.randomId}" + ) - // push "User added" to all group members except for `inviterUserId` - _ ← seqUpdExt.broadcastPeopleUpdate( - (memberIds - cmd.inviteeUserId) - cmd.inviterUserId, // is it right? - membersUpdateObsolete, - pushRules = seqUpdExt.pushRules(isFat = true, Some(PushTexts.Added)), - deliveryId = s"useradded_obsolete_${groupId}_${cmd.randomId}" - ) + // push all "refresh group" updates to inviteeUserId + _ ← FutureExt.ftraverse(inviteeUpdatesNew) { update ⇒ + seqUpdExt.deliverUserUpdate(userId = cmd.inviteeUserId, update) + } + + // push updated members count to all group members + SeqState(seq, state) ← seqUpdExt.broadcastClientUpdate( + userId = cmd.inviterUserId, + authId = cmd.inviterAuthId, + bcastUserIds = (memberIds - cmd.inviterUserId) - cmd.inviteeUserId, + update = membersUpdateNew, + deliveryId = s"useradded_${groupId}_${cmd.randomId}" + ) - // push "User added" to `inviterUserId` - _ ← seqUpdExt.deliverClientUpdate( - cmd.inviterUserId, - cmd.inviterAuthId, - membersUpdateObsolete, - pushRules = seqUpdExt.pushRules(isFat = true, None), - deliveryId = s"useradded_obsolete_${groupId}_${cmd.randomId}" - ) + // push service message to invitee + _ ← seqUpdExt.deliverUserUpdate( + userId = cmd.inviteeUserId, + update = serviceMessageUpdate( + cmd.inviterUserId, + dateMillis, + cmd.randomId, + serviceMessage + ), + deliveryTag = Some(Optimization.GroupV2) + ) + _ ← dialogExt.bump(cmd.inviteeUserId, apiGroupPeer.asModel) + } yield SeqStateDate(seq, state, dateMillis) + + val result: Future[SeqStateDate] = for { + /////////////////////////// + // Groups V1 API updates // + /////////////////////////// + + // push "Invited" to invitee + _ ← seqUpdExt.deliverUserUpdate( + userId = cmd.inviteeUserId, + inviteeUpdateObsolete, + pushRules = seqUpdExt.pushRules(isFat = true, Some(PushTexts.invited(newState.groupType))), + deliveryId = s"invite_obsolete_${groupId}_${cmd.randomId}" + ) - /////////////////////////// - // Groups V2 API updates // - /////////////////////////// + // push "User added" to all group members except for `inviterUserId` + _ ← seqUpdExt.broadcastPeopleUpdate( + (memberIds - cmd.inviteeUserId) - cmd.inviterUserId, // is it right? + membersUpdateObsolete, + pushRules = seqUpdExt.pushRules(isFat = true, Some(PushTexts.Added)), + deliveryId = s"useradded_obsolete_${groupId}_${cmd.randomId}" + ) + + // push "User added" to `inviterUserId` + _ ← seqUpdExt.deliverClientUpdate( + cmd.inviterUserId, + cmd.inviterAuthId, + membersUpdateObsolete, + pushRules = seqUpdExt.pushRules(isFat = true, None), + deliveryId = s"useradded_obsolete_${groupId}_${cmd.randomId}" + ) - seqStateDate ← if (newState.groupType.isChannel) inviteCHANNELUpdates else inviteGROUPUpdates + /////////////////////////// + // Groups V2 API updates // + /////////////////////////// - } yield seqStateDate + seqStateDate ← if (newState.groupType.isChannel) inviteCHANNELUpdates else inviteGROUPUpdates - result pipeTo sender() + } yield seqStateDate + + result pipeTo sender() + } + } } } } @@ -213,135 +221,178 @@ private[group] trait MemberCommandHandlers extends GroupsImplicits { if (state.isMember(cmd.joiningUserId) && !state.isInvited(cmd.joiningUserId)) { sender() ! Status.Failure(GroupErrors.UserAlreadyJoined) } else { - // user was invited in group by other group user - val wasInvited = state.isInvited(cmd.joiningUserId) - - // trying to figure out who invited joining user. - // Descending priority: - // • inviter defined in `Join` command (when invited via token) - // • inviter from members list (when invited by other user) - // • group creator (safe fallback) - val optMember = state.members.get(cmd.joiningUserId) - val inviterUserId = cmd.invitingUserId - .orElse(optMember.map(_.inviterUserId)) - .getOrElse(state.ownerUserId) - - persist(UserJoined(Instant.now, cmd.joiningUserId, inviterUserId)) { evt ⇒ - val newState = commit(evt) - - val date = evt.ts - val dateMillis = date.toEpochMilli - val showJoinMessage = newState.adminSettings.showJoinLeaveMessages - val memberIds = newState.memberIds - val apiMembers = newState.members.values.map(_.asStruct).toVector - val randomId = ACLUtils.randomLong() - - // If user was never invited to group - he don't have group on devices, - // that means we need to push all group-info related updates - // - // If user was invited to group by other member - we don't need to push group updates, - // cause they were pushed already on invite step - // TODO: unify isHistoryShared usage - val joiningUserUpdatesNew: Vector[Update] = { - if (wasInvited) { - Vector.empty[Update] - } else { - val optDrop = if (newState.isHistoryShared) Some(UpdateChatDropCache(apiGroupPeer)) else None - optDrop ++: refreshGroupUpdates(newState, cmd.joiningUserId) - } - } - - // For groups with not async members we should push: - // • Diff for members; - // • Diff for joining user if he was previously invited; - // • Members for joining user if he wasn't previously invited. - // - // For groups with async members we should push: - // • UpdateGroupMembersCountChanged for both joining user and members - val (joiningUpdateNew, membersUpdateNew): (Update, Update) = - if (newState.isAsyncMembers) { - val u = UpdateGroupMembersCountChanged(groupId, newState.membersCount) - (u, u) - } else { - val joiningMember = apiMembers.find(_.userId == cmd.joiningUserId) - val diff = UpdateGroupMemberDiff( - groupId, - addedMembers = joiningMember.toVector, - membersCount = newState.membersCount, - removedUsers = Vector.empty - ) - - if (wasInvited) { - (diff, diff) - } else { - ( - UpdateGroupMembersUpdated(groupId, apiMembers), - diff - ) + val isBlockedFu = checkIsBlocked(cmd.joiningUserId, state.ownerUserId) + + onSuccess(isBlockedFu) { isBlocked ⇒ + if (isBlocked) { + sender() ! Status.Failure(GroupErrors.UserIsBanned) + } else { + // user was invited in group by other group user + val wasInvited = state.isInvited(cmd.joiningUserId) + + // trying to figure out who invited joining user. + // Descending priority: + // • inviter defined in `Join` command (when invited via token) + // • inviter from members list (when invited by other user) + // • group creator (safe fallback) + val optMember = state.members.get(cmd.joiningUserId) + val inviterUserId = cmd.invitingUserId + .orElse(optMember.map(_.inviterUserId)) + .getOrElse(state.ownerUserId) + + persist(UserJoined(Instant.now, cmd.joiningUserId, inviterUserId)) { evt ⇒ + val newState = commit(evt) + + val date = evt.ts + val dateMillis = date.toEpochMilli + val showJoinMessage = newState.adminSettings.showJoinLeaveMessages + val memberIds = newState.memberIds + val apiMembers = newState.members.values.map(_.asStruct).toVector + val randomId = ACLUtils.randomLong() + + // If user was never invited to group - he don't have group on devices, + // that means we need to push all group-info related updates + // + // If user was invited to group by other member - we don't need to push group updates, + // cause they were pushed already on invite step + // TODO: unify isHistoryShared usage + val joiningUserUpdatesNew: Vector[Update] = { + if (wasInvited) { + Vector.empty[Update] + } else { + val optDrop = if (newState.isHistoryShared) Some(UpdateChatDropCache(apiGroupPeer)) else None + optDrop ++: refreshGroupUpdates(newState, cmd.joiningUserId) + } } - } - // TODO: not sure how it should be in old API - val membersUpdateObsolete = UpdateGroupMembersUpdateObsolete(groupId, apiMembers) + // For groups with not async members we should push: + // • Diff for members; + // • Diff for joining user if he was previously invited; + // • Members for joining user if he wasn't previously invited. + // + // For groups with async members we should push: + // • UpdateGroupMembersCountChanged for both joining user and members + val (joiningUpdateNew, membersUpdateNew): (Update, Update) = + if (newState.isAsyncMembers) { + val u = UpdateGroupMembersCountChanged(groupId, newState.membersCount) + (u, u) + } else { + val joiningMember = apiMembers.find(_.userId == cmd.joiningUserId) + val diff = UpdateGroupMemberDiff( + groupId, + addedMembers = joiningMember.toVector, + membersCount = newState.membersCount, + removedUsers = Vector.empty + ) - val serviceMessage = GroupServiceMessages.userJoined + if (wasInvited) { + (diff, diff) + } else { + ( + UpdateGroupMembersUpdated(groupId, apiMembers), + diff + ) + } + } - //TODO: remove deprecated - db.run(GroupUserRepo.create( - groupId, - userId = cmd.joiningUserId, - inviterUserId = inviterUserId, - invitedAt = optMember.map(_.invitedAt).getOrElse(date), - joinedAt = Some(LocalDateTime.now(ZoneOffset.UTC)), - isAdmin = false - ): @silent) + // TODO: not sure how it should be in old API + val membersUpdateObsolete = UpdateGroupMembersUpdateObsolete(groupId, apiMembers) - def joinGROUPUpdates: Future[SeqStateDate] = - for { - // push all group updates to joiningUserId - _ ← FutureExt.ftraverse(joiningUserUpdatesNew) { update ⇒ - seqUpdExt.deliverUserUpdate(userId = cmd.joiningUserId, update) - } + val serviceMessage = GroupServiceMessages.userJoined - // push updated members list/count/difference to joining user, - // make it `FatSeqUpdate` if this user invited to group for first time. - // TODO???: isFat = !wasInvited - is it correct? - SeqState(seq, state) ← seqUpdExt.deliverClientUpdate( + //TODO: remove deprecated + db.run(GroupUserRepo.create( + groupId, userId = cmd.joiningUserId, - authId = cmd.joiningUserAuthId, - update = joiningUpdateNew, - pushRules = seqUpdExt.pushRules(isFat = !wasInvited, None), //!wasInvited means that user came for first time here - deliveryId = s"join_${groupId}_${randomId}" + inviterUserId = inviterUserId, + invitedAt = optMember.map(_.invitedAt).getOrElse(date), + joinedAt = Some(LocalDateTime.now(ZoneOffset.UTC)), + isAdmin = false + ): @silent) - ) + def joinGROUPUpdates: Future[SeqStateDate] = + for { + // push all group updates to joiningUserId + _ ← FutureExt.ftraverse(joiningUserUpdatesNew) { update ⇒ + seqUpdExt.deliverUserUpdate(userId = cmd.joiningUserId, update) + } + + // push updated members list/count/difference to joining user, + // make it `FatSeqUpdate` if this user invited to group for first time. + // TODO???: isFat = !wasInvited - is it correct? + SeqState(seq, state) ← seqUpdExt.deliverClientUpdate( + userId = cmd.joiningUserId, + authId = cmd.joiningUserAuthId, + update = joiningUpdateNew, + pushRules = seqUpdExt.pushRules(isFat = !wasInvited, None), //!wasInvited means that user came for first time here + deliveryId = s"join_${groupId}_${randomId}" - // push updated members list/count to all group members except joiningUserId - _ ← seqUpdExt.broadcastPeopleUpdate( - memberIds - cmd.joiningUserId, - membersUpdateNew, - deliveryId = s"userjoined_${groupId}_${randomId}" - ) + ) - date ← if (showJoinMessage) { - dialogExt.sendServerMessage( - apiGroupPeer, - senderUserId = cmd.joiningUserId, - senderAuthId = cmd.joiningUserAuthId, - randomId = randomId, - serviceMessage // no delivery tag. This updated handled this way in Groups V1 - ) map (_.date) - } else { - // write service message only for joining user - // and push join message + // push updated members list/count to all group members except joiningUserId + _ ← seqUpdExt.broadcastPeopleUpdate( + memberIds - cmd.joiningUserId, + membersUpdateNew, + deliveryId = s"userjoined_${groupId}_${randomId}" + ) + + date ← if (showJoinMessage) { + dialogExt.sendServerMessage( + apiGroupPeer, + senderUserId = cmd.joiningUserId, + senderAuthId = cmd.joiningUserAuthId, + randomId = randomId, + serviceMessage // no delivery tag. This updated handled this way in Groups V1 + ) map (_.date) + } else { + // write service message only for joining user + // and push join message + for { + _ ← dialogExt.writeMessageSelf( + userId = cmd.joiningUserId, + peer = apiGroupPeer, + senderUserId = cmd.joiningUserId, + dateMillis = dateMillis, + randomId = randomId, + serviceMessage + ) + _ ← seqUpdExt.deliverUserUpdate( + userId = cmd.joiningUserId, + update = serviceMessageUpdate( + cmd.joiningUserId, + dateMillis, + randomId, + serviceMessage + ), + deliveryTag = Some(Optimization.GroupV2) + ) + } yield dateMillis + } + } yield SeqStateDate(seq, state, date) + + def joinCHANNELUpdates: Future[SeqStateDate] = for { - _ ← dialogExt.writeMessageSelf( + // push all group updates to joiningUserId + _ ← FutureExt.ftraverse(joiningUserUpdatesNew) { update ⇒ + seqUpdExt.deliverUserUpdate(userId = cmd.joiningUserId, update) + } + + // push updated members count to joining user + SeqState(seq, state) ← seqUpdExt.deliverClientUpdate( userId = cmd.joiningUserId, - peer = apiGroupPeer, - senderUserId = cmd.joiningUserId, - dateMillis = dateMillis, - randomId = randomId, - serviceMessage + authId = cmd.joiningUserAuthId, + update = joiningUpdateNew, + deliveryId = s"join_${groupId}_${randomId}" + ) + + // push updated members count to all group members except joining user + _ ← seqUpdExt.broadcastPeopleUpdate( + memberIds - cmd.joiningUserId, + membersUpdateNew, + deliveryId = s"userjoined_${groupId}_${randomId}" ) + + // push join message only to joining user _ ← seqUpdExt.deliverUserUpdate( userId = cmd.joiningUserId, update = serviceMessageUpdate( @@ -352,69 +403,34 @@ private[group] trait MemberCommandHandlers extends GroupsImplicits { ), deliveryTag = Some(Optimization.GroupV2) ) - } yield dateMillis - } - } yield SeqStateDate(seq, state, date) - - def joinCHANNELUpdates: Future[SeqStateDate] = - for { - // push all group updates to joiningUserId - _ ← FutureExt.ftraverse(joiningUserUpdatesNew) { update ⇒ - seqUpdExt.deliverUserUpdate(userId = cmd.joiningUserId, update) - } - - // push updated members count to joining user - SeqState(seq, state) ← seqUpdExt.deliverClientUpdate( - userId = cmd.joiningUserId, - authId = cmd.joiningUserAuthId, - update = joiningUpdateNew, - deliveryId = s"join_${groupId}_${randomId}" - ) - - // push updated members count to all group members except joining user - _ ← seqUpdExt.broadcastPeopleUpdate( - memberIds - cmd.joiningUserId, - membersUpdateNew, - deliveryId = s"userjoined_${groupId}_${randomId}" - ) + _ ← dialogExt.bump(cmd.joiningUserId, apiGroupPeer.asModel) + } yield SeqStateDate(seq, state, dateMillis) - // push join message only to joining user - _ ← seqUpdExt.deliverUserUpdate( - userId = cmd.joiningUserId, - update = serviceMessageUpdate( - cmd.joiningUserId, - dateMillis, - randomId, - serviceMessage - ), - deliveryTag = Some(Optimization.GroupV2) - ) - _ ← dialogExt.bump(cmd.joiningUserId, apiGroupPeer.asModel) - } yield SeqStateDate(seq, state, dateMillis) - - val result: Future[(SeqStateDate, Vector[Int], Long)] = - for { - /////////////////////////// - // Groups V1 API updates // - /////////////////////////// - - // push update about members to all users, except joining user - _ ← seqUpdExt.broadcastPeopleUpdate( - memberIds - cmd.joiningUserId, - membersUpdateObsolete, - pushRules = seqUpdExt.pushRules(isFat = true, None), - deliveryId = s"userjoined_obsolete_${groupId}_${randomId}" - ) + val result: Future[(SeqStateDate, Vector[Int], Long)] = + for { + /////////////////////////// + // Groups V1 API updates // + /////////////////////////// + + // push update about members to all users, except joining user + _ ← seqUpdExt.broadcastPeopleUpdate( + memberIds - cmd.joiningUserId, + membersUpdateObsolete, + pushRules = seqUpdExt.pushRules(isFat = true, None), + deliveryId = s"userjoined_obsolete_${groupId}_${randomId}" + ) - /////////////////////////// - // Groups V2 API updates // - /////////////////////////// + /////////////////////////// + // Groups V2 API updates // + /////////////////////////// - seqStateDate ← if (newState.groupType.isChannel) joinCHANNELUpdates else joinGROUPUpdates + seqStateDate ← if (newState.groupType.isChannel) joinCHANNELUpdates else joinGROUPUpdates - } yield (seqStateDate, memberIds.toVector :+ inviterUserId, randomId) + } yield (seqStateDate, memberIds.toVector :+ inviterUserId, randomId) - result pipeTo sender() + result pipeTo sender() + } + } } } } diff --git a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupRpcErrors.scala b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupRpcErrors.scala index 74d3940dbe..e3332b1cfc 100644 --- a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupRpcErrors.scala +++ b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupRpcErrors.scala @@ -20,10 +20,12 @@ object GroupRpcErrors { val GroupNotPublic = RpcError(400, "GROUP_IS_NOT_PUBLIC", "The group is not public.", false, None) val CantLeaveGroup = RpcError(403, "CANT_LEAVE_GROUP", "You can't leave this group!", false, None) val CantJoinGroup = RpcError(403, "CANT_JOIN_GROUP", "You can't join this group!", false, None) + val CantGrantToBot = RpcError(400, "CANT_GRANT_TO_BOT", "Can't grant this permissions to bot", false, None) + val UserIsBanned = RpcError(403, "USER_IS_BANNED", "You can't join this group.", false, None) + val InvalidShortName = RpcError(400, "GROUP_SHORT_NAME_INVALID", "Invalid group short name. Valid short name should contain from 5 to 32 characters, and may consist of latin characters, numbers and underscores", false, None) val ShortNameTaken = RpcError(400, "GROUP_SHORT_NAME_TAKEN", "This short name already belongs to other user or group, we are sorry!", false, None) val NoPermission = CommonRpcErrors.forbidden("You have no permission to execute this action") - val CantGrantToBot = RpcError(400, "CANT_GRANT_TO_BOT", "Can't grant this permissions to bot", false, None) } // format: ON diff --git a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupsServiceImpl.scala b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupsServiceImpl.scala index d0c99160f7..8a568a5356 100644 --- a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupsServiceImpl.scala +++ b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/groups/GroupsServiceImpl.scala @@ -645,6 +645,7 @@ final class GroupsServiceImpl(groupInviteConfig: GroupInviteConfig)(implicit act case GroupErrors.ShortNameTaken ⇒ GroupRpcErrors.ShortNameTaken case GroupErrors.NoPermission ⇒ GroupRpcErrors.NoPermission case GroupErrors.CantLeaveGroup ⇒ GroupRpcErrors.CantLeaveGroup + case GroupErrors.UserIsBanned ⇒ GroupRpcErrors.UserIsBanned } } From 293d989fb5f63965a993ef35d23a14cbfdf97876 Mon Sep 17 00:00:00 2001 From: rockjam Date: Fri, 23 Sep 2016 16:49:18 +0300 Subject: [PATCH 370/414] fix(server:groups): timeout fix --- .../actor/server/group/MemberCommandHandlers.scala | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/MemberCommandHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/MemberCommandHandlers.scala index 94f068079c..b3f261c18f 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/MemberCommandHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/MemberCommandHandlers.scala @@ -32,11 +32,13 @@ private[group] trait MemberCommandHandlers extends GroupsImplicits { } else if (state.isMember(cmd.inviteeUserId)) { sender() ! Status.Failure(GroupErrors.UserAlreadyJoined) } else { + val replyTo = sender() + val isBlockedFu = checkIsBlocked(cmd.inviteeUserId, state.ownerUserId) onSuccess(isBlockedFu) { isBlocked ⇒ if (isBlocked) { - sender() ! Status.Failure(GroupErrors.UserIsBanned) + replyTo ! Status.Failure(GroupErrors.UserIsBanned) } else { val inviteeIsExUser = state.isExUser(cmd.inviteeUserId) @@ -204,7 +206,7 @@ private[group] trait MemberCommandHandlers extends GroupsImplicits { } yield seqStateDate - result pipeTo sender() + result pipeTo replyTo } } } @@ -221,11 +223,13 @@ private[group] trait MemberCommandHandlers extends GroupsImplicits { if (state.isMember(cmd.joiningUserId) && !state.isInvited(cmd.joiningUserId)) { sender() ! Status.Failure(GroupErrors.UserAlreadyJoined) } else { + val replyTo = sender() + val isBlockedFu = checkIsBlocked(cmd.joiningUserId, state.ownerUserId) onSuccess(isBlockedFu) { isBlocked ⇒ if (isBlocked) { - sender() ! Status.Failure(GroupErrors.UserIsBanned) + replyTo ! Status.Failure(GroupErrors.UserIsBanned) } else { // user was invited in group by other group user val wasInvited = state.isInvited(cmd.joiningUserId) @@ -428,7 +432,7 @@ private[group] trait MemberCommandHandlers extends GroupsImplicits { } yield (seqStateDate, memberIds.toVector :+ inviterUserId, randomId) - result pipeTo sender() + result pipeTo replyTo } } } From 9ffb194ecdeeb15083df44ff8d974aef60c8097e Mon Sep 17 00:00:00 2001 From: kor_ka Date: Mon, 26 Sep 2016 18:44:16 +0300 Subject: [PATCH 371/414] fix(android): merge emoji keyboard size in cyanogen with soft naw buttons --- .../main/java/im/actor/sdk/util/Screen.java | 4 ++ .../sdk/view/emoji/keyboard/BaseKeyboard.java | 70 ++++++++++++------- 2 files changed, 50 insertions(+), 24 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/util/Screen.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/util/Screen.java index 2cde4c7d73..0a0488ce58 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/util/Screen.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/util/Screen.java @@ -31,6 +31,10 @@ public static int getHeight() { return AndroidContext.getContext().getResources().getDisplayMetrics().heightPixels; } + public static boolean isPrtrait() { + return getWidth() > getHeight(); + } + public static int getStatusBarHeight() { int result = 0; diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/emoji/keyboard/BaseKeyboard.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/emoji/keyboard/BaseKeyboard.java index 05f65d5f59..5fb86599b4 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/emoji/keyboard/BaseKeyboard.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/emoji/keyboard/BaseKeyboard.java @@ -5,6 +5,7 @@ import android.content.res.Configuration; import android.graphics.PixelFormat; import android.graphics.Rect; +import android.os.Build; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; @@ -16,10 +17,13 @@ import android.widget.RelativeLayout; import android.widget.TextView; +import java.lang.reflect.Field; + import im.actor.sdk.R; import im.actor.runtime.Log; import im.actor.sdk.controllers.conversation.KeyboardLayout; import im.actor.sdk.util.KeyboardHelper; +import im.actor.sdk.util.Screen; public class BaseKeyboard implements ViewTreeObserver.OnGlobalLayoutListener { @@ -43,7 +47,7 @@ public class BaseKeyboard implements private KeyboardStatusListener keyboardStatusListener; final WindowManager windowManager; - int keyboardHeight = 0; + int keyboardHeight; private boolean showingPending; private boolean showing = false; @@ -51,6 +55,7 @@ public class BaseKeyboard implements private boolean softwareKeyboardShowing; private KeyboardHelper keyboardHelper; private boolean keyboardMeasured = false; + private int keyboardTriggerHeight = Screen.dp(150); public BaseKeyboard(Activity activity, EditText messageBody) { this.activity = activity; @@ -220,12 +225,11 @@ public void onGlobalLayout() { return; } Rect r = new Rect(); - decorView.getWindowVisibleDisplayFrame(r); + messageBody.getWindowVisibleDisplayFrame(r); - int screenHeight = decorView.getRootView() + int screenHeight = messageBody.getRootView() .getHeight(); - int heightDifference = screenHeight - - (r.bottom - r.top); + // int widthDiff = decorView.getRootView().getWidth() - (r.right - r.left); // if (Math.abs(widthDiff) > 0) { @@ -234,30 +238,22 @@ public void onGlobalLayout() { int resourceId = activity.getResources() .getIdentifier("status_bar_height", "dimen", "android"); + int statusBarHeight = 0; + if (resourceId > 0) { - heightDifference -= activity.getResources() + statusBarHeight = activity.getResources() .getDimensionPixelSize(resourceId); - } - int orientation = activity.getResources().getConfiguration().orientation; - - int id = activity.getResources().getIdentifier("config_showNavigationBar", "bool", "android"); - if (id > 0) { - if (activity.getResources().getBoolean(id)) { - int navbarResId = activity.getResources() - .getIdentifier( - orientation == Configuration.ORIENTATION_PORTRAIT ? "navigation_bar_height" : "navigation_bar_height_landscape", - "dimen", "android"); - if (navbarResId > 0) { - heightDifference -= activity.getResources() - .getDimensionPixelSize(navbarResId); - } - } + screenHeight -= statusBarHeight; } - boolean changed = softwareKeyboardShowing; + screenHeight -= getViewInset(root, statusBarHeight); + + int heightDifference = screenHeight - (r.bottom - r.top); + + boolean changed = softwareKeyboardShowing; - if (heightDifference > 100) { + if (heightDifference > keyboardTriggerHeight) { softwareKeyboardShowing = true; @@ -267,7 +263,6 @@ public void onGlobalLayout() { dismiss(); - } else { softwareKeyboardShowing = false; @@ -340,4 +335,31 @@ public boolean onBackPressed() { } return false; } + + public static int getViewInset(View view, int statusBarHeight) { + if (view == null || view.getRootView() == null) { + return 0; + } + + view = view.getRootView(); + + if (Build.VERSION.SDK_INT < 21 || view.getHeight() == Screen.getHeight() || view.getHeight() == Screen.getHeight() - statusBarHeight) { + return 0; + } + + try { + Field mAttachInfoField = View.class.getDeclaredField("mAttachInfo"); + mAttachInfoField.setAccessible(true); + Object mAttachInfo = mAttachInfoField.get(view); + if (mAttachInfo != null) { + Field mStableInsetsField = mAttachInfo.getClass().getDeclaredField("mStableInsets"); + mStableInsetsField.setAccessible(true); + Rect insets = (Rect) mStableInsetsField.get(mAttachInfo); + return insets.bottom; + } + } catch (Exception e) { + e.printStackTrace(); + } + return 0; + } } From 40689517afe7c3afd8188c5a76b3ca13277a2190 Mon Sep 17 00:00:00 2001 From: rockjam Date: Fri, 23 Sep 2016 20:35:27 +0300 Subject: [PATCH 372/414] fix(server:docker): add templates dir to docker image --- actor-server/Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/actor-server/Dockerfile b/actor-server/Dockerfile index 4025021928..961a13b516 100644 --- a/actor-server/Dockerfile +++ b/actor-server/Dockerfile @@ -2,6 +2,7 @@ FROM openjdk:8u92-jre-alpine MAINTAINER Actor LLC RUN apk --update add bash openssl apr ADD target/docker/stage/var /var +ADD templates /var/lib/actor/templates ENTRYPOINT bin/actor WORKDIR /var/lib/actor EXPOSE 9070 9080 9090 From 136b4aae5eedf6aeca4f43c1325f66528d881d16 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Wed, 28 Sep 2016 13:28:26 +0300 Subject: [PATCH 373/414] chore(android): make toolbar fragment views protected for sdk customization --- .../toolbar/ChatToolbarFragment.java | 25 ++++++++++--------- .../main/java/im/actor/sdk/util/Screen.java | 4 --- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/toolbar/ChatToolbarFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/toolbar/ChatToolbarFragment.java index 7646182437..51f717b745 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/toolbar/ChatToolbarFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/toolbar/ChatToolbarFragment.java @@ -41,38 +41,39 @@ import static im.actor.sdk.util.ActorSDKMessenger.groups; import static im.actor.sdk.util.ActorSDKMessenger.messenger; +import static im.actor.sdk.util.ActorSDKMessenger.myUid; import static im.actor.sdk.util.ActorSDKMessenger.users; public class ChatToolbarFragment extends BaseFragment { public static final int MAX_USERS_FOR_CALLS = 5; - private static final int PERMISSIONS_REQUEST_FOR_CALL = 8; - private static final int PERMISSIONS_REQUEST_FOR_VIDEO_CALL = 12; + protected static final int PERMISSIONS_REQUEST_FOR_CALL = 8; + protected static final int PERMISSIONS_REQUEST_FOR_VIDEO_CALL = 12; public static ChatToolbarFragment create(Peer peer) { return new ChatToolbarFragment(peer); } - private Peer peer; + protected Peer peer; // Toolbar title root view - private View barView; + protected View barView; // Toolbar unread counter - private TextView counter; + protected TextView counter; // Toolbar Avatar view - private AvatarView barAvatar; + protected AvatarView barAvatar; // Toolbar title view - private TextView barTitle; + protected TextView barTitle; // Toolbar subtitle view container - private View barSubtitleContainer; + protected View barSubtitleContainer; // Toolbar subtitle text view - private TextView barSubtitle; + protected TextView barSubtitle; // Toolbar typing container - private View barTypingContainer; + protected View barTypingContainer; // Toolbar typing icon - private ImageView barTypingIcon; + protected ImageView barTypingIcon; // Toolbar typing text - private TextView barTyping; + protected TextView barTyping; public ChatToolbarFragment() { setRootFragment(true); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/util/Screen.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/util/Screen.java index 0a0488ce58..2cde4c7d73 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/util/Screen.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/util/Screen.java @@ -31,10 +31,6 @@ public static int getHeight() { return AndroidContext.getContext().getResources().getDisplayMetrics().heightPixels; } - public static boolean isPrtrait() { - return getWidth() > getHeight(); - } - public static int getStatusBarHeight() { int result = 0; From 92193ca54280bffeb227783f389b0c8afed7dd42 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Wed, 28 Sep 2016 17:18:13 +0300 Subject: [PATCH 374/414] fix(android): remove old chat fragments on handle new intent --- .../im/actor/sdk/controllers/conversation/ChatActivity.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatActivity.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatActivity.java index 5edd333c91..1e834268d2 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatActivity.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatActivity.java @@ -107,6 +107,9 @@ protected void onNewIntent(Intent intent) { protected void handleIntent(Intent intent) { Peer peer = Peer.fromUniqueId(intent.getExtras().getLong(EXTRA_CHAT_PEER)); + if (chatFragment != null) { + getSupportFragmentManager().beginTransaction().remove(chatFragment).commitNow(); + } chatFragment = ActorSDK.sharedActor().getDelegate().fragmentForChat(peer); if (chatFragment == null) { chatFragment = ChatFragment.create(peer); From a4128de5a34823379ce2b750ba9c7235065c2bf4 Mon Sep 17 00:00:00 2001 From: rockjam Date: Wed, 28 Sep 2016 19:42:56 +0300 Subject: [PATCH 375/414] chore(server:docker): add invite link to docker config --- actor-server/src/docker/var/lib/actor/conf/server.conf | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/actor-server/src/docker/var/lib/actor/conf/server.conf b/actor-server/src/docker/var/lib/actor/conf/server.conf index 507ceefe68..840e8b0d81 100644 --- a/actor-server/src/docker/var/lib/actor/conf/server.conf +++ b/actor-server/src/docker/var/lib/actor/conf/server.conf @@ -33,6 +33,14 @@ http { } modules { + messaging { + groups { + invite { + base-uri: ${?ACTOR_INVITE_LINK} + base-uri: "https://quit.email" + } + } + } files { adapter: "im.actor.server.file.local.LocalFileStorageAdapter" } From 059195248022cb2ccfbe38eea0fee3360863fddc Mon Sep 17 00:00:00 2001 From: rockjam Date: Wed, 28 Sep 2016 20:28:28 +0300 Subject: [PATCH 376/414] chore(server:docker): add actor push token --- actor-server/src/docker/var/lib/actor/conf/server.conf | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/actor-server/src/docker/var/lib/actor/conf/server.conf b/actor-server/src/docker/var/lib/actor/conf/server.conf index 840e8b0d81..9e34572879 100644 --- a/actor-server/src/docker/var/lib/actor/conf/server.conf +++ b/actor-server/src/docker/var/lib/actor/conf/server.conf @@ -22,6 +22,13 @@ services { auth-token: ${?ACTOR_GATE_TOKEN} } + actor { + push { + token: "" + token: ${?ACTOR_PUSH_TOKEN} + } + } + file-storage { location: "/files" location: ${?ACTOR_FILESTORAGE_LOCATION} From ca1501e0c0ab81edc8d88b53c19546ec52359e25 Mon Sep 17 00:00:00 2001 From: rockjam Date: Wed, 28 Sep 2016 22:36:53 +0300 Subject: [PATCH 377/414] fix(server): config change --- actor-server/src/docker/var/lib/actor/conf/server.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actor-server/src/docker/var/lib/actor/conf/server.conf b/actor-server/src/docker/var/lib/actor/conf/server.conf index 9e34572879..7cf2dd61ab 100644 --- a/actor-server/src/docker/var/lib/actor/conf/server.conf +++ b/actor-server/src/docker/var/lib/actor/conf/server.conf @@ -43,8 +43,8 @@ modules { messaging { groups { invite { + base-uri: "https://example.com" base-uri: ${?ACTOR_INVITE_LINK} - base-uri: "https://quit.email" } } } From e2826378234bdfaa765802986d1b4819bbc82f2f Mon Sep 17 00:00:00 2001 From: kor_ka Date: Tue, 4 Oct 2016 15:23:21 +0300 Subject: [PATCH 378/414] fix(core): handle userCanInvite group admin settings --- .../im/actor/sdk/controllers/group/GroupPermissionsFragment.java | 1 + 1 file changed, 1 insertion(+) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupPermissionsFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupPermissionsFragment.java index 3a51d0dbb7..ce659f76c7 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupPermissionsFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/group/GroupPermissionsFragment.java @@ -157,6 +157,7 @@ public boolean onOptionsItemSelected(MenuItem item) { permissions.setMembersCanEditInfo(canEditInfo.isChecked()); permissions.setAdminsCanEditGroupInfo(canAdminsEditInfo.isChecked()); permissions.setShowJoinLeaveMessages(showLeaveJoin.isChecked()); + permissions.setMembersCanInvite(canSendInvitations.isChecked()); permissions.setShowAdminsToMembers(showAdminsToMembers.isChecked()); execute(messenger().saveGroupPermissions(groupId, permissions).then(r -> { finishActivity(); From 58e9b5237bda05a78f956c892a59e1d8743108bc Mon Sep 17 00:00:00 2001 From: kor_ka Date: Tue, 4 Oct 2016 15:39:58 +0300 Subject: [PATCH 379/414] fix(android): change input overlay text if group is deleted --- .../actor/sdk/controllers/conversation/ChatFragment.java | 7 +++++++ .../android-sdk/src/main/res/values/ui_text.xml | 2 ++ 2 files changed, 9 insertions(+) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatFragment.java index 90f2a7154f..c9afd92537 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatFragment.java @@ -207,6 +207,13 @@ public void onResume() { inputOverlayText.setEnabled(true); showView(inputOverlayContainer, false); goneView(inputContainer, false); + } else if (groupVM.getIsDeleted().get()) { + inputOverlayText.setText(groupVM.getGroupType() == GroupType.CHANNEL ? R.string.channel_deleted : R.string.group_deleted); + inputOverlayText.setTextColor(style.getListActionColor()); + inputOverlayText.setClickable(false); + inputOverlayText.setEnabled(false); + showView(inputOverlayContainer, false); + goneView(inputContainer, false); } else { inputOverlayText.setText(R.string.chat_not_member); inputOverlayText.setTextColor(style.getListActionColor()); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml index b6bd18a968..f5d7860a6b 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml @@ -275,6 +275,8 @@ SLIDE TO CANCEL You are not a member of this group + Group deleted + Channel deleted Join Mute From 13b84c7152315a3b9495ab61f58bd8358be1c063 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Tue, 4 Oct 2016 17:58:58 +0300 Subject: [PATCH 380/414] fix(android): append file extension to uploading file name (fixing video playing on ios) --- .../main/java/im/actor/core/AndroidMessenger.java | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/actor-sdk/sdk-core/core/core-android/src/main/java/im/actor/core/AndroidMessenger.java b/actor-sdk/sdk-core/core/core-android/src/main/java/im/actor/core/AndroidMessenger.java index f217534513..3619a97928 100644 --- a/actor-sdk/sdk-core/core/core-android/src/main/java/im/actor/core/AndroidMessenger.java +++ b/actor-sdk/sdk-core/core/core-android/src/main/java/im/actor/core/AndroidMessenger.java @@ -319,6 +319,8 @@ public Command sendUri(final Peer peer, final Uri uri, String appName) String mimeType; String fileName; + String ext = ""; + Cursor cursor = context.getContentResolver().query(uri, filePathColumn, null, null, null); if (cursor != null) { cursor.moveToFirst(); @@ -331,7 +333,8 @@ public Command sendUri(final Peer peer, final Uri uri, String appName) fileName = new File(uri.getPath()).getName(); int index = fileName.lastIndexOf("."); if (index > 0) { - mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(fileName.substring(index + 1)); + ext = fileName.substring(index + 1); + mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(ext); } else { mimeType = "?/?"; } @@ -354,8 +357,11 @@ public Command sendUri(final Peer peer, final Uri uri, String appName) "/"); dest.mkdirs(); - boolean isGif = picturePath != null && picturePath.endsWith(".gif"); - File outputFile = new File(dest, "upload_" + random.nextLong() + (isGif ? ".gif" : ".jpg")); + if (ext.isEmpty() && picturePath != null) { + int index = picturePath.lastIndexOf("."); + ext = picturePath.substring(index + 1); + } + File outputFile = new File(dest, "upload_" + random.nextLong() + "." + ext); picturePath = outputFile.getAbsolutePath(); try { @@ -371,6 +377,8 @@ public Command sendUri(final Peer peer, final Uri uri, String appName) fileName = picturePath; } + if (!ext.isEmpty() && !fileName.endsWith(ext)) + fileName += "." + ext; if (mimeType.startsWith("video/")) { sendVideo(peer, picturePath, fileName); // trackVideoSend(peer); From 259481ff03f10aec842283a4be0a20de13440c17 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Thu, 6 Oct 2016 17:45:48 +0300 Subject: [PATCH 381/414] fix(android): recover read_contacts permission request --- .../sdk/controllers/root/RootActivity.java | 33 +++++++++++++++++++ .../tools/MediaPickerFragment.java | 23 +++++++++++++ .../java/im/actor/core/AndroidMessenger.java | 9 +++++ 3 files changed, 65 insertions(+) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/root/RootActivity.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/root/RootActivity.java index 1ef914f25c..1afc353039 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/root/RootActivity.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/root/RootActivity.java @@ -1,11 +1,16 @@ package im.actor.sdk.controllers.root; +import android.Manifest; import android.content.Intent; +import android.content.pm.PackageManager; import android.graphics.drawable.ColorDrawable; import android.os.Bundle; +import android.support.v13.app.ActivityCompat; import android.support.v4.app.Fragment; +import android.support.v4.content.ContextCompat; import android.support.v7.widget.Toolbar; +import im.actor.core.viewmodel.AppStateVM; import im.actor.sdk.ActorSDK; import im.actor.sdk.R; import im.actor.sdk.controllers.activity.BaseFragmentActivity; @@ -16,11 +21,19 @@ */ public class RootActivity extends BaseFragmentActivity { + private static final int PERMISSIONS_REQUEST_READ_CONTACTS = 1; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_root); + if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) { + ActivityCompat.requestPermissions(this, + new String[]{Manifest.permission.READ_CONTACTS}, + PERMISSIONS_REQUEST_READ_CONTACTS); + } + // // Configure Toolbar // @@ -49,4 +62,24 @@ protected void onNewIntent(Intent intent) { super.onNewIntent(intent); InviteHandler.handleIntent(this, intent); } + + @Override + public void onRequestPermissionsResult(int requestCode, + String permissions[], int[] grantResults) { + switch (requestCode) { + case PERMISSIONS_REQUEST_READ_CONTACTS: { + // If request is cancelled, the result arrays are empty. + if (grantResults.length > 0 + && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + + AppStateVM appStateVM = ActorSDK.sharedActor().getMessenger().getAppStateVM(); + if (appStateVM.isDialogsLoaded() && appStateVM.isContactsLoaded() && appStateVM.isSettingsLoaded()) { + ActorSDK.sharedActor().getMessenger().startImport(); + } + + } + } + + } + } } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/tools/MediaPickerFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/tools/MediaPickerFragment.java index 741e983201..b37df569e0 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/tools/MediaPickerFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/tools/MediaPickerFragment.java @@ -40,6 +40,7 @@ public class MediaPickerFragment extends BaseFragment { private static final int REQUEST_LOCATION = 4; private static final int REQUEST_CONTACT = 5; private static final int PERMISSIONS_REQUEST_CAMERA = 6; + private static final int PERMISSIONS_REQUEST_CONTACTS = 7; private String pendingFile; private boolean pickCropped; @@ -135,6 +136,22 @@ public void requestLocation() { public void requestContact() { this.pickCropped = false; + // + // Checking permissions + // + Activity activity = getActivity(); + if (activity != null) { + if (Build.VERSION.SDK_INT >= 23) { + if (ContextCompat.checkSelfPermission(activity, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) { + requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, + PERMISSIONS_REQUEST_CONTACTS); + return; + } + } + } else { + return; + } + Intent intent = new Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI); startActivityForResult(intent, REQUEST_CONTACT); } @@ -277,6 +294,12 @@ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permis requestPhoto(); } } + + if (requestCode == PERMISSIONS_REQUEST_CONTACTS) { + if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + requestContact(); + } + } } @Override diff --git a/actor-sdk/sdk-core/core/core-android/src/main/java/im/actor/core/AndroidMessenger.java b/actor-sdk/sdk-core/core/core-android/src/main/java/im/actor/core/AndroidMessenger.java index 3619a97928..7505a2d4ad 100644 --- a/actor-sdk/sdk-core/core/core-android/src/main/java/im/actor/core/AndroidMessenger.java +++ b/actor-sdk/sdk-core/core/core-android/src/main/java/im/actor/core/AndroidMessenger.java @@ -37,6 +37,7 @@ import im.actor.core.utils.AppStateActor; import im.actor.core.utils.IOUtils; import im.actor.core.utils.ImageHelper; +import im.actor.core.viewmodel.AppStateVM; import im.actor.core.viewmodel.Command; import im.actor.core.viewmodel.GalleryVM; import im.actor.runtime.Runtime; @@ -546,4 +547,12 @@ public EventBus getEvents() { return modules.getEvents(); } + public AppStateVM getAppStateVM() { + return modules.getConductor().getAppStateVM(); + } + + public void startImport() { + modules.getContactsModule().startImport(); + } + } \ No newline at end of file From ae22da4e94795619ae2d2d893b13deb9a2556411 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Thu, 6 Oct 2016 18:44:17 +0300 Subject: [PATCH 382/414] chore(android): update empty dialogs/contacts hints --- .../android-sdk/src/main/res/values-ru/ui_text.xml | 4 ++-- .../android-sdk/src/main/res/values/ui_text.xml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-ru/ui_text.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-ru/ui_text.xml index 455d2067ee..2b12068b11 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-ru/ui_text.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-ru/ui_text.xml @@ -222,7 +222,7 @@ Нажмите и удерживайте для дополнительной информации Начать общение - Выберите контакт из списка контактов или нажмине кнопку "плюс" для начала общения. + Выберите контакт из списка контактов или нажмине кнопку "написать" для начала общения. Посмотреть контакт Переименовать контакт @@ -252,7 +252,7 @@ Пригласите своих друзей Никто из ваших контактов не использует {appName}. Используйте кнопку ниже что бы пригласить их. РАССКАЗАТЬ ДРУЗЬЯМ - или добавьте контакт вручную, нажав на плюс ниже + или добавьте контакт вручную, используя поиск на главном экране Отправить ссылку diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml index f5d7860a6b..f7c17c7e06 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml @@ -208,7 +208,7 @@ Chats Long press for additional options Start messaging - Pick a contact from your contact list or press the plus button to start a conversation right now. + Pick a contact from your contact list or press the "write" button to start a conversation right now. View contact Rename contact @@ -233,7 +233,7 @@ Invite your friends None of your contacts are using {appName}. Use the button below to invite them. Tell a friend - or manually add contacts by pressing the plus button below. + or manually add contacts using global search on main screen. Remove {0} from contacts Removing… From f0861cbaf595131db8f00ceb6a26c0e255769c9c Mon Sep 17 00:00:00 2001 From: rockjam Date: Thu, 6 Oct 2016 17:54:13 +0300 Subject: [PATCH 383/414] feat(server): firebase implementation --- .../src/main/protobuf/sequence.proto | 6 +- .../src/main/resources/reference.conf | 7 +- .../im/actor/server/push/PushProvider.scala | 3 + .../{sequence => push/apple}/APNSSend.scala | 3 +- .../apple}/ApplePushConfig.scala | 6 +- .../apple}/ApplePushExtension.scala | 5 +- .../apple}/ApplePushProvider.scala | 9 +- .../server/push/google/DeliveryStream.scala | 77 +++++++ .../push/google/FirebasePushExtension.scala | 42 ++++ .../server/push/google/GCMPushExtension.scala | 41 ++++ .../push/google/GooglePushDelivery.scala | 66 ++++++ .../push/google/GooglePushMessage.scala | 8 + .../google}/GooglePushProvider.scala | 29 ++- .../im/actor/server/push/google/configs.scala | 31 +++ .../im/actor/server/push/google/package.scala | 29 +++ .../server/sequence/GooglePushExtension.scala | 209 ------------------ .../actor/server/sequence/PushProvider.scala | 3 - .../server/sequence/SeqUpdatesExtension.scala | 2 + .../im/actor/server/sequence/VendorPush.scala | 69 +++--- .../sequence/operations/PushOperations.scala | 30 ++- .../actor/server/webrtc/WebrtcCallActor.scala | 40 +++- .../src/main/protobuf/model/push.proto | 12 +- .../server/model/push/PushCredentials.scala | 4 + .../push/FirebasePushCredentialsKV.scala | 65 ++++++ .../push/GooglePushCredentialsRepo.scala | 10 +- .../rpc/service/push/PushServiceImpl.scala | 53 +++-- .../rpc/service/auth/AuthServiceSpec.scala | 2 +- .../usr/share/actor/conf/server.conf.example | 13 +- 28 files changed, 565 insertions(+), 309 deletions(-) create mode 100644 actor-server/actor-core/src/main/scala/im/actor/server/push/PushProvider.scala rename actor-server/actor-core/src/main/scala/im/actor/server/{sequence => push/apple}/APNSSend.scala (96%) rename actor-server/actor-core/src/main/scala/im/actor/server/{sequence => push/apple}/ApplePushConfig.scala (96%) rename actor-server/actor-core/src/main/scala/im/actor/server/{sequence => push/apple}/ApplePushExtension.scala (97%) rename actor-server/actor-core/src/main/scala/im/actor/server/{sequence => push/apple}/ApplePushProvider.scala (92%) create mode 100644 actor-server/actor-core/src/main/scala/im/actor/server/push/google/DeliveryStream.scala create mode 100644 actor-server/actor-core/src/main/scala/im/actor/server/push/google/FirebasePushExtension.scala create mode 100644 actor-server/actor-core/src/main/scala/im/actor/server/push/google/GCMPushExtension.scala create mode 100644 actor-server/actor-core/src/main/scala/im/actor/server/push/google/GooglePushDelivery.scala create mode 100644 actor-server/actor-core/src/main/scala/im/actor/server/push/google/GooglePushMessage.scala rename actor-server/actor-core/src/main/scala/im/actor/server/{sequence => push/google}/GooglePushProvider.scala (52%) create mode 100644 actor-server/actor-core/src/main/scala/im/actor/server/push/google/configs.scala create mode 100644 actor-server/actor-core/src/main/scala/im/actor/server/push/google/package.scala delete mode 100644 actor-server/actor-core/src/main/scala/im/actor/server/sequence/GooglePushExtension.scala delete mode 100644 actor-server/actor-core/src/main/scala/im/actor/server/sequence/PushProvider.scala create mode 100644 actor-server/actor-persist/src/main/scala/im/actor/server/persist/push/FirebasePushCredentialsKV.scala diff --git a/actor-server/actor-core/src/main/protobuf/sequence.proto b/actor-server/actor-core/src/main/protobuf/sequence.proto index c1203289aa..d0c415f0a5 100644 --- a/actor-server/actor-core/src/main/protobuf/sequence.proto +++ b/actor-server/actor-core/src/main/protobuf/sequence.proto @@ -87,9 +87,10 @@ message UserSequenceCommands { option (scalapb.message).extends = "im.actor.server.sequence.VendorPushCommand"; oneof creds { - GooglePushCredentials google = 1; + GCMPushCredentials gcm = 1; ApplePushCredentials apple = 2; ActorPushCredentials actor = 3; + FirebasePushCredentials firebase = 4; } } @@ -97,9 +98,10 @@ message UserSequenceCommands { option (scalapb.message).extends = "im.actor.server.sequence.VendorPushCommand"; oneof creds { - GooglePushCredentials google = 1; + GCMPushCredentials gcm = 1; ApplePushCredentials apple = 2; ActorPushCredentials actor = 3; + FirebasePushCredentials firebase = 4; } } diff --git a/actor-server/actor-core/src/main/resources/reference.conf b/actor-server/actor-core/src/main/resources/reference.conf index 55d3a0e650..7510ed83bc 100644 --- a/actor-server/actor-core/src/main/resources/reference.conf +++ b/actor-server/actor-core/src/main/resources/reference.conf @@ -27,9 +27,10 @@ services { } google { - push { - max-connections: 20 - + gcm { + keys: [] + } + firebase { keys: [] } } diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/push/PushProvider.scala b/actor-server/actor-core/src/main/scala/im/actor/server/push/PushProvider.scala new file mode 100644 index 0000000000..126ebfc967 --- /dev/null +++ b/actor-server/actor-core/src/main/scala/im/actor/server/push/PushProvider.scala @@ -0,0 +1,3 @@ +package im.actor.server.push + +private[push] trait PushProvider diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/sequence/APNSSend.scala b/actor-server/actor-core/src/main/scala/im/actor/server/push/apple/APNSSend.scala similarity index 96% rename from actor-server/actor-core/src/main/scala/im/actor/server/sequence/APNSSend.scala rename to actor-server/actor-core/src/main/scala/im/actor/server/push/apple/APNSSend.scala index dac84bad73..8ae184cb3d 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/sequence/APNSSend.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/push/apple/APNSSend.scala @@ -1,10 +1,11 @@ -package im.actor.server.sequence +package im.actor.server.push.apple import akka.actor.ActorSystem import com.google.protobuf.wrappers.{ Int32Value, StringValue } import com.relayrides.pushy.apns.PushNotificationResponse import com.relayrides.pushy.apns.util.{ SimpleApnsPushNotification, TokenUtil } import im.actor.server.model.push.ApplePushCredentials +import im.actor.server.sequence.PushFutureListener import io.netty.util.concurrent.{ Future ⇒ NFuture } import scodec.bits.BitVector diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/sequence/ApplePushConfig.scala b/actor-server/actor-core/src/main/scala/im/actor/server/push/apple/ApplePushConfig.scala similarity index 96% rename from actor-server/actor-core/src/main/scala/im/actor/server/sequence/ApplePushConfig.scala rename to actor-server/actor-core/src/main/scala/im/actor/server/push/apple/ApplePushConfig.scala index 57bc5e3f5c..dabea62cea 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/sequence/ApplePushConfig.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/push/apple/ApplePushConfig.scala @@ -1,7 +1,7 @@ -package im.actor.server.sequence +package im.actor.server.push.apple -import com.typesafe.config.Config import com.github.kxbmap.configs.syntax._ +import com.typesafe.config.Config import scala.collection.JavaConversions._ @@ -34,4 +34,4 @@ object ApnsCert { isVoip = config.getOrElse[Boolean]("voip", false) ) } -} \ No newline at end of file +} diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/sequence/ApplePushExtension.scala b/actor-server/actor-core/src/main/scala/im/actor/server/push/apple/ApplePushExtension.scala similarity index 97% rename from actor-server/actor-core/src/main/scala/im/actor/server/sequence/ApplePushExtension.scala rename to actor-server/actor-core/src/main/scala/im/actor/server/push/apple/ApplePushExtension.scala index ff0cfb4dd0..04f12cab82 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/sequence/ApplePushExtension.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/push/apple/ApplePushExtension.scala @@ -1,4 +1,4 @@ -package im.actor.server.sequence +package im.actor.server.push.apple import java.io.File import java.util.concurrent.{ ExecutionException, TimeUnit, TimeoutException } @@ -13,9 +13,8 @@ import im.actor.server.persist.push.ApplePushCredentialsRepo import im.actor.util.log.AnyRefLogSource import scala.collection.concurrent.TrieMap -import scala.concurrent.Future -import scala.concurrent.blocking import scala.concurrent.duration._ +import scala.concurrent.{ Future, blocking } import scala.util.Try object ApplePushExtension extends ExtensionId[ApplePushExtension] with ExtensionIdProvider { diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/sequence/ApplePushProvider.scala b/actor-server/actor-core/src/main/scala/im/actor/server/push/apple/ApplePushProvider.scala similarity index 92% rename from actor-server/actor-core/src/main/scala/im/actor/server/sequence/ApplePushProvider.scala rename to actor-server/actor-core/src/main/scala/im/actor/server/push/apple/ApplePushProvider.scala index a3f261ea35..a4ffdd5441 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/sequence/ApplePushProvider.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/push/apple/ApplePushProvider.scala @@ -1,14 +1,15 @@ -package im.actor.server.sequence +package im.actor.server.push.apple import akka.actor.ActorSystem import akka.event.Logging -import com.google.protobuf.wrappers.{ Int32Value, StringValue } import com.relayrides.pushy.apns.ApnsClient import com.relayrides.pushy.apns.util.{ ApnsPayloadBuilder, SimpleApnsPushNotification } import im.actor.server.dialog.DialogExtension import im.actor.server.model.push.ApplePushCredentials +import im.actor.server.push.PushProvider +import im.actor.server.sequence.PushData -private[sequence] final class ApplePushProvider(userId: Int)(implicit system: ActorSystem) extends PushProvider with APNSSend { +final class ApplePushProvider(userId: Int)(implicit system: ActorSystem) extends PushProvider with APNSSend { import system.dispatcher private val log = Logging(system, getClass) @@ -81,4 +82,4 @@ private[sequence] final class ApplePushProvider(userId: Int)(implicit system: Ac } } -} \ No newline at end of file +} diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/push/google/DeliveryStream.scala b/actor-server/actor-core/src/main/scala/im/actor/server/push/google/DeliveryStream.scala new file mode 100644 index 0000000000..09450343c8 --- /dev/null +++ b/actor-server/actor-core/src/main/scala/im/actor/server/push/google/DeliveryStream.scala @@ -0,0 +1,77 @@ +package im.actor.server.push.google + +import akka.actor.{ ActorRef, ActorSystem } +import akka.event.Logging +import akka.stream.actor.ActorPublisher +import akka.stream.scaladsl.{ Flow, Source } +import akka.{ Done, NotUsed } +import cats.data.Xor +import im.actor.server.push.google.GooglePushDelivery.Delivery +import io.circe.parser +import spray.client.pipelining._ +import spray.http.{ HttpCharsets, StatusCodes } + +import scala.concurrent.Future +import scala.util.{ Failure, Success } + +private[google] final class DeliveryStream(publisher: ActorRef, serviceName: String, remove: String ⇒ Future[_])(implicit system: ActorSystem) { + import system.dispatcher + + private val log = Logging(system, getClass) + + private implicit val mat = tolerantMaterializer + + log.debug("Starting {} stream", serviceName) + + val stream: Future[Done] = Source + .fromPublisher(ActorPublisher[NotificationDelivery](publisher)) + .via(flow) + .runForeach { + // TODO: flatten + case Xor.Right((body, delivery)) ⇒ + parser.parse(body) match { + case Xor.Right(json) ⇒ + json.asObject match { + case Some(obj) ⇒ + obj("error") flatMap (_.asString) match { + case Some("InvalidRegistration") ⇒ + log.warning("{}: Invalid registration, deleting", serviceName) + remove(delivery.m.to) + case Some("NotRegistered") ⇒ + log.warning("{}: Token is not registered, deleting", serviceName) + remove(delivery.m.to) + case Some(other) ⇒ + log.warning("{}: Error in response: {}", serviceName, other) + case None ⇒ + log.debug("{}: Successfully delivered: {}", serviceName, delivery) + } + case None ⇒ + log.error("{}: Expected JSON Object but got: {}", serviceName, json) + } + case Xor.Left(failure) ⇒ log.error(failure.underlying, "{}: Failed to parse response", serviceName) + } + case Xor.Left(e) ⇒ + log.error(e, "{}: Failed to make request", serviceName) + } + + stream onComplete { + case Failure(e) ⇒ + log.error(e, "{}: Failure in stream", serviceName) + case Success(_) ⇒ + log.debug("{}: Stream completed", serviceName) + } + + private def flow(implicit system: ActorSystem): Flow[NotificationDelivery, Xor[RuntimeException, (String, Delivery)], NotUsed] = { + import system.dispatcher + val pipeline = sendReceive + Flow[NotificationDelivery].mapAsync(2) { + case (req, del) ⇒ + pipeline(req) map { resp ⇒ + if (resp.status == StatusCodes.OK) + Xor.Right(resp.entity.data.asString(HttpCharsets.`UTF-8`) → del) + else + Xor.Left(new RuntimeException(s"Failed to deliver message, StatusCode was not OK: ${resp.status}")) + } + } + } +} diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/push/google/FirebasePushExtension.scala b/actor-server/actor-core/src/main/scala/im/actor/server/push/google/FirebasePushExtension.scala new file mode 100644 index 0000000000..14b3cc12d7 --- /dev/null +++ b/actor-server/actor-core/src/main/scala/im/actor/server/push/google/FirebasePushExtension.scala @@ -0,0 +1,42 @@ +package im.actor.server.push.google + +import akka.actor._ +import akka.event.Logging +import im.actor.server.model.push.FirebasePushCredentials +import im.actor.server.persist.push.FirebasePushCredentialsKV + +import scala.concurrent.Future + +object FirebasePushExtension extends ExtensionId[FirebasePushExtension] with ExtensionIdProvider { + override def createExtension(system: ExtendedActorSystem): FirebasePushExtension = new FirebasePushExtension(system) + + override def lookup(): ExtensionId[_ <: Extension] = FirebasePushExtension +} + +final class FirebasePushExtension(system: ActorSystem) extends Extension { + + private val log = Logging(system, getClass) + + private val firebaseKV = new FirebasePushCredentialsKV()(system) + + private val config = GooglePushManagerConfig.loadFirebase.get + + private val firebasePublisher = system.actorOf(GooglePushDelivery.props("https://fcm.googleapis.com/fcm/send"), "fcm-delivery") + + private val firebaseStream = new DeliveryStream(firebasePublisher, "Firebase", remove)(system).stream + + def send(projectId: Long, message: GooglePushMessage): Unit = + config.keyMap get projectId match { + case Some(key) ⇒ + firebasePublisher ! GooglePushDelivery.Delivery(message, key) + case None ⇒ + log.warning("Key not found for projectId: {}", projectId) + } + + private def remove(regId: String): Future[Unit] = + firebaseKV.deleteByToken(regId) + + def fetchCreds(authIds: Set[Long]): Future[Seq[FirebasePushCredentials]] = + firebaseKV.find(authIds) + +} diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/push/google/GCMPushExtension.scala b/actor-server/actor-core/src/main/scala/im/actor/server/push/google/GCMPushExtension.scala new file mode 100644 index 0000000000..9386497dad --- /dev/null +++ b/actor-server/actor-core/src/main/scala/im/actor/server/push/google/GCMPushExtension.scala @@ -0,0 +1,41 @@ +package im.actor.server.push.google + +import akka.actor._ +import akka.event.Logging +import im.actor.server.db.DbExtension +import im.actor.server.model.push.GCMPushCredentials +import im.actor.server.persist.push.GooglePushCredentialsRepo + +import scala.concurrent.Future + +object GCMPushExtension extends ExtensionId[GCMPushExtension] with ExtensionIdProvider { + override def createExtension(system: ExtendedActorSystem): GCMPushExtension = new GCMPushExtension(system) + + override def lookup(): ExtensionId[_ <: Extension] = GCMPushExtension +} + +final class GCMPushExtension(system: ActorSystem) extends Extension { + + private val log = Logging(system, getClass) + private val db = DbExtension(system).db + + private val config = GooglePushManagerConfig.loadGCM.get + + private val gcmPublisher = system.actorOf(GooglePushDelivery.props("https://gcm-http.googleapis.com/gcm/send"), "gcm-delivery") + + private val gcmStream = new DeliveryStream(gcmPublisher, "GCM", remove)(system).stream + + def send(projectId: Long, message: GooglePushMessage): Unit = + config.keyMap get projectId match { + case Some(key) ⇒ + gcmPublisher ! GooglePushDelivery.Delivery(message, key) + case None ⇒ + log.warning("Key not found for projectId: {}", projectId) + } + + private def remove(regId: String): Future[Int] = + db.run(GooglePushCredentialsRepo.deleteByToken(regId)) + + def fetchCreds(authIds: Set[Long]): Future[Seq[GCMPushCredentials]] = + db.run(GooglePushCredentialsRepo.find(authIds)) +} diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/push/google/GooglePushDelivery.scala b/actor-server/actor-core/src/main/scala/im/actor/server/push/google/GooglePushDelivery.scala new file mode 100644 index 0000000000..278b63ba9a --- /dev/null +++ b/actor-server/actor-core/src/main/scala/im/actor/server/push/google/GooglePushDelivery.scala @@ -0,0 +1,66 @@ +package im.actor.server.push.google + +import akka.actor.{ ActorLogging, Props } +import akka.stream.actor.{ ActorPublisher, ActorPublisherMessage } +import io.circe.generic.auto._ +import io.circe.syntax._ +import spray.http.HttpHeaders.Authorization +import spray.http.HttpMethods.POST +import spray.http._ + +import scala.annotation.tailrec + +private[google] object GooglePushDelivery { + final case class Delivery(m: GooglePushMessage, key: String) + + private val MaxQueue = 100000 + + def props(apiUri: String) = Props(classOf[GooglePushDelivery], apiUri) +} + +private final class GooglePushDelivery(apiUri: String) extends ActorPublisher[NotificationDelivery] with ActorLogging { + import ActorPublisherMessage._ + import GooglePushDelivery._ + + private[this] var buf = Vector.empty[NotificationDelivery] + private[this] val uri = Uri(apiUri) + + def receive = { + case d: Delivery if buf.size == MaxQueue ⇒ + log.error("Current queue is already at size MaxQueue: {}, totalDemand: {}, ignoring delivery", MaxQueue, totalDemand) + deliverBuf() + case d: Delivery ⇒ + log.debug("Trying to deliver google push. Queue size: {}, totalDemand: {}", buf.size, totalDemand) + if (buf.isEmpty && totalDemand > 0) { + onNext(mkJob(d)) + } else { + this.buf :+= mkJob(d) + deliverBuf() + } + case Request(n) ⇒ + log.debug("Trying to deliver google push. Queue size: {}, totalDemand: {}, subscriber requests {} elements", buf.size, totalDemand, n) + deliverBuf() + } + + @tailrec private def deliverBuf(): Unit = + if (totalDemand > 0) { + if (totalDemand <= Int.MaxValue) { + val (use, keep) = buf.splitAt(totalDemand.toInt) + buf = keep + use foreach onNext + } else { + val (use, keep) = buf.splitAt(Int.MaxValue) + buf = keep + use foreach onNext + deliverBuf() + } + } + + private def mkJob(d: Delivery): NotificationDelivery = + HttpRequest( + method = POST, + uri = uri, + headers = List(Authorization(GenericHttpCredentials(s"key=${d.key}", Map.empty[String, String]))), + entity = HttpEntity(ContentTypes.`application/json`, d.m.asJson.noSpaces) + ) → d +} diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/push/google/GooglePushMessage.scala b/actor-server/actor-core/src/main/scala/im/actor/server/push/google/GooglePushMessage.scala new file mode 100644 index 0000000000..a95d276fe4 --- /dev/null +++ b/actor-server/actor-core/src/main/scala/im/actor/server/push/google/GooglePushMessage.scala @@ -0,0 +1,8 @@ +package im.actor.server.push.google + +final case class GooglePushMessage( + to: String, + collapse_key: Option[String], + data: Option[Map[String, String]], + time_to_live: Option[Int] +) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/sequence/GooglePushProvider.scala b/actor-server/actor-core/src/main/scala/im/actor/server/push/google/GooglePushProvider.scala similarity index 52% rename from actor-server/actor-core/src/main/scala/im/actor/server/sequence/GooglePushProvider.scala rename to actor-server/actor-core/src/main/scala/im/actor/server/push/google/GooglePushProvider.scala index 8e4d6a437a..ebcd5bfafe 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/sequence/GooglePushProvider.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/push/google/GooglePushProvider.scala @@ -1,10 +1,13 @@ -package im.actor.server.sequence +package im.actor.server.push.google import akka.actor.ActorSystem -import im.actor.server.model.push.GooglePushCredentials +import im.actor.server.model.push.{ FirebasePushCredentials, GCMPushCredentials, GooglePushCredentials } +import im.actor.server.push.PushProvider +import im.actor.server.sequence.PushData -private[sequence] final class GooglePushProvider(userId: Int, system: ActorSystem) extends PushProvider { - private val googlePushExt = GooglePushExtension(system) +final class GooglePushProvider(userId: Int, system: ActorSystem) extends PushProvider { + private val gcmPushExt = GCMPushExtension(system) + private val firebasePushExt = FirebasePushExtension(system) def deliverInvisible(seq: Int, creds: GooglePushCredentials): Unit = { val message = GooglePushMessage( @@ -13,8 +16,12 @@ private[sequence] final class GooglePushProvider(userId: Int, system: ActorSyste data = Some(Map("seq" → seq.toString)), time_to_live = None ) - - googlePushExt.send(creds.projectId, message) + creds match { + case _: GCMPushCredentials ⇒ + gcmPushExt.send(creds.projectId, message) + case _: FirebasePushCredentials ⇒ + firebasePushExt.send(creds.projectId, message) + } } def deliverVisible( @@ -37,7 +44,11 @@ private[sequence] final class GooglePushProvider(userId: Int, system: ActorSyste )), time_to_live = None ) - - googlePushExt.send(creds.projectId, message) + creds match { + case _: GCMPushCredentials ⇒ + gcmPushExt.send(creds.projectId, message) + case _: FirebasePushCredentials ⇒ + firebasePushExt.send(creds.projectId, message) + } } -} \ No newline at end of file +} diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/push/google/configs.scala b/actor-server/actor-core/src/main/scala/im/actor/server/push/google/configs.scala new file mode 100644 index 0000000000..d27dfbb048 --- /dev/null +++ b/actor-server/actor-core/src/main/scala/im/actor/server/push/google/configs.scala @@ -0,0 +1,31 @@ +package im.actor.server.push.google + +import com.typesafe.config.{ Config, ConfigException } +import com.github.kxbmap.configs.syntax._ +import im.actor.config.ActorConfig + +import scala.util.Try + +private final case class GooglePushKey(projectId: Long, key: String) + +private[google] final case class GooglePushManagerConfig(keys: List[GooglePushKey]) { + def keyMap: Map[Long, String] = + (keys map { + case GooglePushKey(projectId, key) ⇒ projectId → key + }).toMap +} + +private[google] object GooglePushManagerConfig { + + def loadGCM: Try[GooglePushManagerConfig] = { + val config = ActorConfig.load() + load(config.getConfig("services.google.gcm")) recoverWith { + case e: ConfigException.Missing ⇒ load(config.getConfig("services.google.push")) // legacy conf, before firebase + } + } + + def loadFirebase: Try[GooglePushManagerConfig] = + load(ActorConfig.load().getConfig("services.google.firebase")) + + private def load(config: Config) = Try(config.extract[GooglePushManagerConfig]) +} diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/push/google/package.scala b/actor-server/actor-core/src/main/scala/im/actor/server/push/google/package.scala new file mode 100644 index 0000000000..2526361b31 --- /dev/null +++ b/actor-server/actor-core/src/main/scala/im/actor/server/push/google/package.scala @@ -0,0 +1,29 @@ +package im.actor.server.push + +import akka.actor.ActorSystem +import akka.stream.{ ActorMaterializer, ActorMaterializerSettings, Supervision } +import spray.http.HttpRequest + +import scala.concurrent._ + +package object google { + + type NotificationDelivery = (HttpRequest, GooglePushDelivery.Delivery) + + def tolerantMaterializer(implicit system: ActorSystem): ActorMaterializer = { + val streamDecider: Supervision.Decider = { + case e: TimeoutException ⇒ + system.log.warning("Timeout in stream, RESUME {}", e) + Supervision.Resume + case e: RuntimeException ⇒ + system.log.warning("Got runtime exception in stream, RESUME {}", e) + Supervision.Resume + case e ⇒ + system.log.error(e, "Got exception in stream, STOP") + Supervision.Stop + } + ActorMaterializer(ActorMaterializerSettings(system) + .withSupervisionStrategy(streamDecider)) + } + +} diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/sequence/GooglePushExtension.scala b/actor-server/actor-core/src/main/scala/im/actor/server/sequence/GooglePushExtension.scala deleted file mode 100644 index 690a816494..0000000000 --- a/actor-server/actor-core/src/main/scala/im/actor/server/sequence/GooglePushExtension.scala +++ /dev/null @@ -1,209 +0,0 @@ -package im.actor.server.sequence - -import akka.NotUsed -import akka.actor._ -import akka.event.Logging -import akka.stream.{ ActorMaterializer, ActorMaterializerSettings, Supervision } -import akka.stream.actor.{ ActorPublisher, ActorPublisherMessage } -import akka.stream.scaladsl.{ Flow, Source } -import cats.data.Xor -import com.github.kxbmap.configs.syntax._ -import com.typesafe.config.Config -import im.actor.server.db.DbExtension -import im.actor.server.model.push.GooglePushCredentials -import im.actor.server.persist.push.GooglePushCredentialsRepo -import io.circe.generic.auto._ -import io.circe.jawn._ -import io.circe.syntax._ -import spray.client.pipelining._ -import spray.http.HttpHeaders.Authorization -import spray.http._ - -import scala.annotation.tailrec -import scala.concurrent.{ Future, TimeoutException } -import scala.util.{ Failure, Success, Try } - -private final case class GooglePushKey(projectId: Long, key: String) - -private object GooglePushKey { - def load(config: Config): Try[GooglePushKey] = { - for { - projectId ← config.get[Try[Long]]("project-id") - key ← config.get[Try[String]]("key") - } yield GooglePushKey(projectId, key) - } -} - -private final case class GooglePushManagerConfig(keys: List[GooglePushKey]) - -private object GooglePushManagerConfig { - def load(googlePushConfig: Config): Try[GooglePushManagerConfig] = - for { - keyConfigs ← googlePushConfig.get[Try[List[Config]]]("keys") - keys ← Try(keyConfigs map (GooglePushKey.load(_).get)) - } yield GooglePushManagerConfig(keys) -} - -final case class GooglePushMessage( - to: String, - collapse_key: Option[String], - data: Option[Map[String, String]], - time_to_live: Option[Int] -) - -object GooglePushExtension extends ExtensionId[GooglePushExtension] with ExtensionIdProvider { - override def createExtension(system: ExtendedActorSystem): GooglePushExtension = new GooglePushExtension(system) - - override def lookup(): ExtensionId[_ <: Extension] = GooglePushExtension -} - -final class GooglePushExtension(system: ActorSystem) extends Extension { - - import system.dispatcher - - private implicit val _system = system - private val log = Logging(system, getClass) - private val db = DbExtension(system).db - - private val streamDecider: Supervision.Decider = { - case e: TimeoutException ⇒ - log.warning("Got timeout exception in stream, RESUME {}", e) - Supervision.Resume - case e: RuntimeException ⇒ - log.warning("Got runtime exception in stream, RESUME {}", e) - Supervision.Resume - case e ⇒ - log.error(e, "Got exception in stream, STOP") - Supervision.Stop - } - private implicit val mat = ActorMaterializer(ActorMaterializerSettings(system).withSupervisionStrategy(streamDecider)) - - private val config = GooglePushManagerConfig.load(system.settings.config.getConfig("services.google.push")).get - private val deliveryPublisher = system.actorOf(GooglePushDelivery.props, "google-push-delivery") - - Source.fromPublisher(ActorPublisher[(HttpRequest, GooglePushDelivery.Delivery)](deliveryPublisher)) - .via(GooglePushDelivery.flow) - .runForeach { - // TODO: flatten - case Xor.Right((body, delivery)) ⇒ - parse(body) match { - case Xor.Right(json) ⇒ - json.asObject match { - case Some(obj) ⇒ - obj("error") flatMap (_.asString) match { - case Some("InvalidRegistration") ⇒ - log.warning("Invalid registration, deleting") - remove(delivery.m.to) - case Some("NotRegistered") ⇒ - log.warning("Token is not registered, deleting") - remove(delivery.m.to) - case Some(other) ⇒ - log.warning("Error in GCM response: {}", other) - case None ⇒ - log.debug("Successfully delivered: {}", delivery) - } - case None ⇒ - log.error("Expected JSON Object but got: {}", json) - } - case Xor.Left(failure) ⇒ log.error(failure.underlying, "Failed to parse response") - } - case Xor.Left(e) ⇒ - log.error(e, "Failed to make request") - } onComplete { - case Failure(e) ⇒ - log.error(e, "Failure in stream, continue with next element") - case Success(_) ⇒ log.debug("Stream completed") - } - - private def remove(regId: String): Future[Int] = db.run(GooglePushCredentialsRepo.deleteByToken(regId)) - - private val keys: Map[Long, String] = - (config.keys map { - case GooglePushKey(projectId, key) ⇒ projectId → key - }).toMap - - def send(projectId: Long, message: GooglePushMessage): Unit = - keys get projectId match { - case Some(key) ⇒ - deliveryPublisher ! GooglePushDelivery.Delivery(message, key) - case None ⇒ - log.warning("Key not found for projectId: {}", projectId) - } - - def fetchCreds(authIds: Set[Long]): Future[Seq[GooglePushCredentials]] = - db.run(GooglePushCredentialsRepo.find(authIds)) -} - -private object GooglePushDelivery { - - object Tick - - final case class Delivery(m: GooglePushMessage, key: String) - - private val MaxQueue = 100000 - - def props = Props(classOf[GooglePushDelivery]) - - def flow(implicit system: ActorSystem): Flow[(HttpRequest, Delivery), Xor[RuntimeException, (String, Delivery)], NotUsed] = { - import system.dispatcher - val pipeline = sendReceive - Flow[(HttpRequest, GooglePushDelivery.Delivery)].mapAsync(2) { - case (req, del) ⇒ - pipeline(req) map { resp ⇒ - if (resp.status == StatusCodes.OK) - Xor.Right(resp.entity.data.asString(HttpCharsets.`UTF-8`) → del) - else - Xor.Left(new RuntimeException(s"Failed to deliver message, StatusCode was not OK: ${resp.status}")) - } - } - } -} - -private final class GooglePushDelivery extends ActorPublisher[(HttpRequest, GooglePushDelivery.Delivery)] with ActorLogging { - - import GooglePushDelivery._ - import ActorPublisherMessage._ - - private[this] var buf = Vector.empty[(HttpRequest, Delivery)] - private val uri = Uri("https://gcm-http.googleapis.com/gcm/send") - - def receive = { - case d: Delivery if buf.size == MaxQueue ⇒ - log.error("Current queue is already at size MaxQueue: {}, totalDemand: {}, ignoring delivery", MaxQueue, totalDemand) - deliverBuf() - case d: Delivery ⇒ - log.debug("Trying to deliver google push. Queue size: {}, totalDemand: {}", buf.size, totalDemand) - if (buf.isEmpty && totalDemand > 0) { - onNext(mkJob(d)) - } else { - this.buf :+= mkJob(d) - deliverBuf() - } - case Request(n) ⇒ - log.debug("Trying to deliver google push. Queue size: {}, totalDemand: {}, subscriber requests {} elements", buf.size, totalDemand, n) - deliverBuf() - } - - @tailrec def deliverBuf(): Unit = - if (totalDemand > 0) { - if (totalDemand <= Int.MaxValue) { - val (use, keep) = buf.splitAt(totalDemand.toInt) - buf = keep - use foreach onNext - } else { - val (use, keep) = buf.splitAt(Int.MaxValue) - buf = keep - use foreach onNext - deliverBuf() - } - } - - private def mkJob(d: Delivery): (HttpRequest, Delivery) = { - HttpRequest( - method = HttpMethods.POST, - uri = uri, - headers = List(Authorization(GenericHttpCredentials(s"key=${d.key}", Map.empty[String, String]))), - entity = HttpEntity(ContentTypes.`application/json`, d.m.asJson.noSpaces) - ) → d - } -} diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/sequence/PushProvider.scala b/actor-server/actor-core/src/main/scala/im/actor/server/sequence/PushProvider.scala deleted file mode 100644 index 185948907c..0000000000 --- a/actor-server/actor-core/src/main/scala/im/actor/server/sequence/PushProvider.scala +++ /dev/null @@ -1,3 +0,0 @@ -package im.actor.server.sequence - -private[sequence] trait PushProvider diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/sequence/SeqUpdatesExtension.scala b/actor-server/actor-core/src/main/scala/im/actor/server/sequence/SeqUpdatesExtension.scala index ee7837b359..ee6de9f22c 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/sequence/SeqUpdatesExtension.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/sequence/SeqUpdatesExtension.scala @@ -10,6 +10,7 @@ import com.google.protobuf.wrappers.Int32Value import im.actor.server.db.DbExtension import im.actor.server.migrations.v2.{ MigrationNameList, MigrationTsActions } import im.actor.server.model._ +import im.actor.server.persist.push.FirebasePushCredentialsKV import im.actor.server.sequence.operations.{ DeliveryOperations, DifferenceOperations, PushOperations } import im.actor.storage.SimpleStorage import im.actor.storage.api.{ GetAction, UpsertAction } @@ -57,6 +58,7 @@ final class SeqUpdatesExtension(_system: ActorSystem) lazy val region: SeqUpdatesManagerRegion = SeqUpdatesManagerRegion.start()(system) private val writer = system.actorOf(BatchUpdatesWriter.props, "batch-updates-writer") private val mediator = DistributedPubSub(system).mediator + protected lazy val firebaseKv = new FirebasePushCredentialsKV val MultiSequenceMigrationTs: Long = { val optTs = MigrationTsActions.getTimestamp(MigrationNameList.MultiSequence)(conn) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/sequence/VendorPush.scala b/actor-server/actor-core/src/main/scala/im/actor/server/sequence/VendorPush.scala index b36cebd8ca..b940b8d6dc 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/sequence/VendorPush.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/sequence/VendorPush.scala @@ -2,19 +2,21 @@ package im.actor.server.sequence import akka.actor._ import akka.pattern.pipe +import im.actor.concurrent.FutureExt import im.actor.server.db.DbExtension -import im.actor.server.model.push.{ ActorPushCredentials, ApplePushCredentials, GooglePushCredentials, PushCredentials } -import im.actor.server.model.{ DeviceType, Peer, PeerType } -import im.actor.server.persist.AuthSessionRepo +import im.actor.server.model.push._ +import im.actor.server.model.{ DeviceType, Peer } +import im.actor.server.persist.{ AuthIdRepo, AuthSessionRepo } import im.actor.server.persist.configs.ParameterRepo -import im.actor.server.persist.push.{ ActorPushCredentialsRepo, ApplePushCredentialsRepo, GooglePushCredentialsRepo } +import im.actor.server.persist.push.{ ActorPushCredentialsRepo, ApplePushCredentialsRepo, FirebasePushCredentialsKV, GooglePushCredentialsRepo } import im.actor.server.push.actor.ActorPush +import im.actor.server.push.apple.ApplePushProvider +import im.actor.server.push.google.GooglePushProvider import im.actor.server.sequence.UserSequenceCommands.ReloadSettings import im.actor.server.userconfig.SettingsKeys import slick.dbio.DBIO import scala.concurrent.Future -import scala.util.control.NoStackTrace private[sequence] trait VendorPushCommand @@ -114,12 +116,15 @@ private[sequence] final class VendorPush(userId: Int) extends Actor with ActorLo private val settingsControl = context.actorOf(SettingsControl.props(userId), "settings") private val googlePushProvider = new GooglePushProvider(userId, context.system) + private val applePushProvider = new ApplePushProvider(userId)(context.system) private val actorPushProvider = ActorPush(context.system) private var mapping: Map[PushCredentials, PushCredentialsInfo] = Map.empty private var notificationSettings = AllNotificationSettings() + private val firebaseKv = new FirebasePushCredentialsKV()(context.system) + init() def receive = initializing @@ -142,14 +147,18 @@ private[sequence] final class VendorPush(userId: Int) extends Actor with ActorLo register(r.getActor) case r: RegisterPushCredentials if r.creds.isApple ⇒ register(r.getApple) - case r: RegisterPushCredentials if r.creds.isGoogle ⇒ - register(r.getGoogle) + case r: RegisterPushCredentials if r.creds.isGcm ⇒ + register(r.getGcm) + case r: RegisterPushCredentials if r.creds.isFirebase ⇒ + register(r.getFirebase) case u: UnregisterPushCredentials if u.creds.isActor ⇒ unregister(u.getActor) case u: UnregisterPushCredentials if u.creds.isApple ⇒ unregister(u.getApple) - case u: UnregisterPushCredentials if u.creds.isGoogle ⇒ - unregister(u.getGoogle) + case u: UnregisterPushCredentials if u.creds.isGcm ⇒ + unregister(u.getGcm) + case u: UnregisterPushCredentials if u.creds.isFirebase ⇒ + unregister(u.getFirebase) case DeliverPush(authId, seq, rules) ⇒ deliver(authId, seq, rules.getOrElse(PushRules())) case r: ReloadSettings ⇒ @@ -165,18 +174,23 @@ private[sequence] final class VendorPush(userId: Int) extends Actor with ActorLo private def init(): Unit = { log.debug("Initializing") - db.run(for { - googleCreds ← GooglePushCredentialsRepo.findByUser(userId) - appleCreds ← ApplePushCredentialsRepo.findByUser(userId) - actorCreds ← ActorPushCredentialsRepo.findByUser(userId) - google ← DBIO.sequence(googleCreds map withInfo) map (_.flatten) - apple ← DBIO.sequence(appleCreds.filterNot(_.isVoip) map withInfo) map (_.flatten) - actor ← DBIO.sequence(actorCreds map withInfo) map (_.flatten) - } yield Initialized(apple ++ google ++ actor)) pipeTo self + (for { + modelAuthIds ← db.run(AuthIdRepo.findByUserId(userId)) + authIds = (modelAuthIds map (_.id)).toSet + gcmCreds ← db.run(GooglePushCredentialsRepo.find(authIds)) + firebaseCreds ← firebaseKv.find(authIds) + appleCreds ← db.run(ApplePushCredentialsRepo.find(authIds)) + actorCreds ← db.run(ActorPushCredentialsRepo.find(authIds)) + + gcm ← FutureExt.ftraverse(gcmCreds)(withInfo) map (_.flatten) + firebase ← FutureExt.ftraverse(firebaseCreds)(withInfo) map (_.flatten) + apple ← FutureExt.ftraverse(appleCreds.filterNot(_.isVoip))(withInfo) map (_.flatten) + actor ← FutureExt.ftraverse(actorCreds)(withInfo) map (_.flatten) + } yield Initialized(apple ++ gcm ++ actor)) pipeTo self } /** - * Delivers a push to credentials associated with given `authId` according to push `rules`` + * Delivers a push to credentials associated with given `authId` according to push `rules` * */ private def deliver(authId: Long, seq: Int, rules: PushRules): Unit = { @@ -311,14 +325,12 @@ private[sequence] final class VendorPush(userId: Int) extends Actor with ActorLo } private def register(creds: PushCredentials): Unit = - db.run { - withInfo(creds) map (_.getOrElse(throw new RuntimeException(s"Cannot find appId for $creds"))) - } pipeTo self + withInfo(creds) map (_.getOrElse(throw new RuntimeException(s"Cannot find appId for $creds"))) pipeTo self - private def withInfo(c: PushCredentials): DBIO[Option[(PushCredentials, PushCredentialsInfo)]] = - for { + private def withInfo(c: PushCredentials): Future[Option[(PushCredentials, PushCredentialsInfo)]] = + db.run(for { authSessionOpt ← AuthSessionRepo.findByAuthId(c.authId) - } yield authSessionOpt map (s ⇒ c → PushCredentialsInfo(s.appId, c.authId)) + } yield authSessionOpt map (s ⇒ c → PushCredentialsInfo(s.appId, c.authId))) private def remove(creds: PushCredentials): Unit = mapping -= creds @@ -327,10 +339,11 @@ private[sequence] final class VendorPush(userId: Int) extends Actor with ActorLo val replyTo = sender() if (mapping.contains(creds)) { remove(creds) - val removeFu = db.run(creds match { - case c: GooglePushCredentials ⇒ GooglePushCredentialsRepo.deleteByToken(c.regId) - case c: ApplePushCredentials ⇒ ApplePushCredentialsRepo.deleteByToken(c.token.toByteArray) - case c: ActorPushCredentials ⇒ ActorPushCredentialsRepo.deleteByTopic(c.endpoint) + val removeFu = (creds match { + case c: GCMPushCredentials ⇒ db.run(GooglePushCredentialsRepo.deleteByToken(c.regId)) + case c: FirebasePushCredentials ⇒ firebaseKv.deleteByToken(c.regId) + case c: ApplePushCredentials ⇒ db.run(ApplePushCredentialsRepo.deleteByToken(c.token.toByteArray)) + case c: ActorPushCredentials ⇒ db.run(ActorPushCredentialsRepo.deleteByTopic(c.endpoint)) }) map (_ ⇒ UnregisterPushCredentialsAck()) pipeTo replyTo removeFu onFailure { diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/sequence/operations/PushOperations.scala b/actor-server/actor-core/src/main/scala/im/actor/server/sequence/operations/PushOperations.scala index ec913292cb..f394b898ed 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/sequence/operations/PushOperations.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/sequence/operations/PushOperations.scala @@ -2,21 +2,23 @@ package im.actor.server.sequence.operations import akka.http.scaladsl.util.FastFuture import im.actor.server.model.AuthSession -import im.actor.server.model.push.{ ActorPushCredentials, ApplePushCredentials, GooglePushCredentials, PushCredentials } +import im.actor.server.model.push._ import im.actor.server.persist.AuthSessionRepo import im.actor.server.persist.push.{ ActorPushCredentialsRepo, ApplePushCredentialsRepo, GooglePushCredentialsRepo } import im.actor.server.sequence.SeqUpdatesExtension import im.actor.server.sequence.UserSequenceCommands.{ Envelope, RegisterPushCredentials, UnregisterPushCredentials, UnregisterPushCredentialsAck } import scodec.bits.BitVector - import akka.pattern.ask import scala.concurrent.Future trait PushOperations { this: SeqUpdatesExtension ⇒ - def registerGooglePushCredentials(creds: GooglePushCredentials) = - registerPushCredentials(creds.authId, RegisterPushCredentials().withGoogle(creds)) + def registerGCMPushCredentials(creds: GCMPushCredentials) = + registerPushCredentials(creds.authId, RegisterPushCredentials().withGcm(creds)) + + def registerFirebasePushCredentials(creds: FirebasePushCredentials) = + registerPushCredentials(creds.authId, RegisterPushCredentials().withFirebase(creds)) def registerApplePushCredentials(creds: ApplePushCredentials) = registerPushCredentials(creds.authId, RegisterPushCredentials().withApple(creds)) @@ -54,10 +56,19 @@ trait PushOperations { this: SeqUpdatesExtension ⇒ FastFuture.successful(()) } - def unregisterGooglePushCredentials(token: String): Future[Unit] = + def unregisterGCMPushCredentials(token: String): Future[Unit] = db.run(GooglePushCredentialsRepo.findByToken(token)) flatMap { case Some(creds) ⇒ - unregisterPushCredentials(creds.authId, UnregisterPushCredentials().withGoogle(creds)) + unregisterPushCredentials(creds.authId, UnregisterPushCredentials().withGcm(creds)) + case None ⇒ + log.warning("Google push credentials not found for token: {}", token) + FastFuture.successful(()) + } + + def unregisterFirebasePushCredentials(token: String): Future[Unit] = + firebaseKv.findByToken(token) flatMap { + case Some(creds) ⇒ + unregisterPushCredentials(creds.authId, UnregisterPushCredentials().withFirebase(creds)) case None ⇒ log.warning("Google push credentials not found for token: {}", token) FastFuture.successful(()) @@ -69,9 +80,10 @@ trait PushOperations { this: SeqUpdatesExtension ⇒ } private def makeUnregister: PartialFunction[PushCredentials, UnregisterPushCredentials] = { - case actor: ActorPushCredentials ⇒ UnregisterPushCredentials().withActor(actor) - case apple: ApplePushCredentials ⇒ UnregisterPushCredentials().withApple(apple) - case google: GooglePushCredentials ⇒ UnregisterPushCredentials().withGoogle(google) + case actor: ActorPushCredentials ⇒ UnregisterPushCredentials().withActor(actor) + case apple: ApplePushCredentials ⇒ UnregisterPushCredentials().withApple(apple) + case gcm: GCMPushCredentials ⇒ UnregisterPushCredentials().withGcm(gcm) + case firebase: FirebasePushCredentials ⇒ UnregisterPushCredentials().withFirebase(firebase) } private def findAllPushCredentials(authId: Long): Future[Seq[PushCredentials]] = diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/webrtc/WebrtcCallActor.scala b/actor-server/actor-core/src/main/scala/im/actor/server/webrtc/WebrtcCallActor.scala index 3778eb92eb..57e676238d 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/webrtc/WebrtcCallActor.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/webrtc/WebrtcCallActor.scala @@ -14,6 +14,8 @@ import im.actor.server.eventbus.{ EventBus, EventBusExtension } import im.actor.server.group.GroupExtension import im.actor.server.model.{ Peer, PeerType } import im.actor.server.push.actor.{ ActorPush, ActorPushMessage } +import im.actor.server.push.apple.{ APNSSend, ApplePushExtension } +import im.actor.server.push.google.{ FirebasePushExtension, GCMPushExtension, GooglePushMessage } import im.actor.server.sequence._ import im.actor.server.user.UserExtension import im.actor.server.values.ValuesExtension @@ -170,7 +172,8 @@ private final class WebrtcCallActor extends StashingActor with ActorLogging with private val groupExt = GroupExtension(system) private val valuesExt = ValuesExtension(system) private val apnsExt = ApplePushExtension(system) - private val gcmExt = GooglePushExtension(system) + private val gcmExt = GCMPushExtension(system) + private val firebaseExt = FirebasePushExtension(system) private val actorPush = ActorPush(system) private val webrtcExt = WebrtcExtension(system) @@ -544,22 +547,26 @@ private final class WebrtcCallActor extends StashingActor with ActorLogging with private def scheduleIncomingCallUpdates(callees: Seq[UserId]): Future[Unit] = { val pushCredsFu = for { authIdsMap ← userExt.getAuthIdsMap(callees.toSet) - acredsMap ← FutureExt.ftraverse(authIdsMap.toSeq) { + appleCredsMap ← FutureExt.ftraverse(authIdsMap.toSeq) { case (userId, authIds) ⇒ apnsExt.fetchVoipCreds(authIds.toSet) map (userId → _) } - gcredsMap ← FutureExt.ftraverse(authIdsMap.toSeq) { + gcmCredsMap ← FutureExt.ftraverse(authIdsMap.toSeq) { case (userId, authIds) ⇒ gcmExt.fetchCreds(authIds.toSet) map (userId → _) } + firebaseCredsMap ← FutureExt.ftraverse(authIdsMap.toSeq) { + case (userId, authIds) ⇒ + firebaseExt.fetchCreds(authIds.toSet) map (userId → _) + } actorCredsMap ← FutureExt.ftraverse(authIdsMap.toSeq) { case (userId, authIds) ⇒ actorPush.fetchCreds(userId) map (userId → _) } - } yield (acredsMap, gcredsMap, actorCredsMap) + } yield (appleCredsMap, gcmCredsMap, firebaseCredsMap, actorCredsMap) pushCredsFu map { - case (appleCreds, googleCreds, actorCreds) ⇒ + case (appleCreds, gcmCreds, firebaseCreds, actorCreds) ⇒ for { (userId, credsList) ← appleCreds creds ← credsList @@ -572,18 +579,27 @@ private final class WebrtcCallActor extends StashingActor with ActorLogging with _ = clientFu foreach { implicit c ⇒ sendNotification(payload, creds, userId) } } yield () + def googlePushMessage(regId: String) = GooglePushMessage( + regId, + None, + Some(Map("callId" → id.toString, "attemptIndex" → "1")), + time_to_live = Some(0) + ) + for { - (member, creds) ← googleCreds + (member, creds) ← gcmCreds cred ← creds - message = new GooglePushMessage( - cred.regId, - None, - Some(Map("callId" → id.toString, "attemptIndex" → "1")), - time_to_live = Some(0) - ) + message = googlePushMessage(cred.regId) _ = gcmExt.send(cred.projectId, message) } yield () + for { + (member, creds) ← firebaseCreds + cred ← creds + message = googlePushMessage(cred.regId) + _ = firebaseExt.send(cred.projectId, message) + } yield () + for { (member, creds) ← actorCreds cred ← creds diff --git a/actor-server/actor-models/src/main/protobuf/model/push.proto b/actor-server/actor-models/src/main/protobuf/model/push.proto index 519a51f64a..71667d4f8f 100644 --- a/actor-server/actor-models/src/main/protobuf/model/push.proto +++ b/actor-server/actor-models/src/main/protobuf/model/push.proto @@ -5,8 +5,16 @@ option java_package = "im.actor.server.model"; import "scalapb/scalapb.proto"; import "google/protobuf/wrappers.proto"; -message GooglePushCredentials { - option (scalapb.message).extends = "im.actor.server.model.push.PushCredentials"; +message GCMPushCredentials { + option (scalapb.message).extends = "im.actor.server.model.push.GooglePushCredentials"; + + int64 auth_id = 1; + int64 project_id = 2; + string reg_id = 3; +} + +message FirebasePushCredentials { + option (scalapb.message).extends = "im.actor.server.model.push.GooglePushCredentials"; int64 auth_id = 1; int64 project_id = 2; diff --git a/actor-server/actor-models/src/main/scala/im/actor/server/model/push/PushCredentials.scala b/actor-server/actor-models/src/main/scala/im/actor/server/model/push/PushCredentials.scala index 6a15866d3b..2f9fc26212 100644 --- a/actor-server/actor-models/src/main/scala/im/actor/server/model/push/PushCredentials.scala +++ b/actor-server/actor-models/src/main/scala/im/actor/server/model/push/PushCredentials.scala @@ -4,3 +4,7 @@ trait PushCredentials { val authId: Long } +trait GooglePushCredentials extends PushCredentials { + val projectId: Long + val regId: String +} diff --git a/actor-server/actor-persist/src/main/scala/im/actor/server/persist/push/FirebasePushCredentialsKV.scala b/actor-server/actor-persist/src/main/scala/im/actor/server/persist/push/FirebasePushCredentialsKV.scala new file mode 100644 index 0000000000..5fff148c6a --- /dev/null +++ b/actor-server/actor-persist/src/main/scala/im/actor/server/persist/push/FirebasePushCredentialsKV.scala @@ -0,0 +1,65 @@ +package im.actor.server.persist.push + +import akka.actor.ActorSystem +import akka.http.scaladsl.util.FastFuture +import com.google.protobuf.wrappers.Int64Value +import im.actor.server.db.DbExtension +import im.actor.server.model.push.FirebasePushCredentials +import im.actor.storage.SimpleStorage + +import scala.concurrent.Future + +// authId -> FirebasePushCredentials +private object FirebaseAuthIdCreds extends SimpleStorage("firebase_auth_id_creds") + +// token -> authId +private object FirebaseTokenAuthId extends SimpleStorage("firebase_token_auth_id") + +final class FirebasePushCredentialsKV(implicit system: ActorSystem) { + import system.dispatcher + + private val (db, conn) = { + val ext = DbExtension(system) + (ext.db, ext.connector) + } + + def createOrUpdate(creds: FirebasePushCredentials): Future[Unit] = + for { + _ ← conn.run( + FirebaseAuthIdCreds.upsert(creds.authId.toString, creds.toByteArray) + ) + _ ← conn.run( + FirebaseTokenAuthId.upsert(creds.regId, Int64Value(creds.authId).toByteArray) + ) + } yield () + + def deleteByToken(token: String): Future[Unit] = + for { + authIdOpt ← findAuthIdByToken(token) + _ ← authIdOpt map { authId ⇒ + delete(token, authId) + } getOrElse FastFuture.successful(None) + } yield () + + def findByToken(token: String): Future[Option[FirebasePushCredentials]] = + for { + authIdOpt ← findAuthIdByToken(token) + creds ← (authIdOpt map find) getOrElse FastFuture.successful(None) + } yield creds + + def find(authId: Long): Future[Option[FirebasePushCredentials]] = + conn.run(FirebaseAuthIdCreds.get(authId.toString)) map (_.map(FirebasePushCredentials.parseFrom)) + + def find(authIds: Set[Long]): Future[Seq[FirebasePushCredentials]] = + Future.sequence(authIds map find) map (_.flatten.toSeq) + + private def findAuthIdByToken(token: String): Future[Option[Long]] = + conn.run(FirebaseTokenAuthId.get(token)) map (_.map(e ⇒ Int64Value.parseFrom(e).value)) + + private def delete(token: String, authId: Long): Future[Unit] = + for { + _ ← conn.run(FirebaseAuthIdCreds.delete(authId.toString)) + _ ← conn.run(FirebaseTokenAuthId.delete(token)) + } yield () + +} diff --git a/actor-server/actor-persist/src/main/scala/im/actor/server/persist/push/GooglePushCredentialsRepo.scala b/actor-server/actor-persist/src/main/scala/im/actor/server/persist/push/GooglePushCredentialsRepo.scala index 9c631daff6..c3e892d314 100644 --- a/actor-server/actor-persist/src/main/scala/im/actor/server/persist/push/GooglePushCredentialsRepo.scala +++ b/actor-server/actor-persist/src/main/scala/im/actor/server/persist/push/GooglePushCredentialsRepo.scala @@ -1,26 +1,26 @@ package im.actor.server.persist.push -import im.actor.server.model.push.GooglePushCredentials +import im.actor.server.model.push.GCMPushCredentials import im.actor.server.persist.AuthIdRepo import slick.driver.PostgresDriver.api._ import scala.concurrent.ExecutionContext import scala.language.postfixOps -final class GooglePushCredentialsTable(tag: Tag) extends Table[GooglePushCredentials](tag, "google_push_credentials") { +final class GooglePushCredentialsTable(tag: Tag) extends Table[GCMPushCredentials](tag, "google_push_credentials") { def authId = column[Long]("auth_id", O.PrimaryKey) def projectId = column[Long]("project_id") def regId = column[String]("reg_id") - def * = (authId, projectId, regId) <> ((GooglePushCredentials.apply _).tupled, GooglePushCredentials.unapply) + def * = (authId, projectId, regId) <> ((GCMPushCredentials.apply _).tupled, GCMPushCredentials.unapply) } object GooglePushCredentialsRepo { private val creds = TableQuery[GooglePushCredentialsTable] - def createOrUpdate(c: GooglePushCredentials) = + def createOrUpdate(c: GCMPushCredentials) = creds.insertOrUpdate(c) private def byAuthId(authId: Rep[Long]) = creds.filter(_.authId === authId) @@ -39,7 +39,7 @@ object GooglePushCredentialsRepo { creds ← find(authIds map (_.id) toSet) } yield creds - def findByToken(token: String): DBIO[Option[GooglePushCredentials]] = + def findByToken(token: String): DBIO[Option[GCMPushCredentials]] = creds.filter(_.regId === token).result.headOption def delete(authId: Long) = diff --git a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/push/PushServiceImpl.scala b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/push/PushServiceImpl.scala index 44caa34d3f..6d31c2007b 100644 --- a/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/push/PushServiceImpl.scala +++ b/actor-server/actor-rpc-api/src/main/scala/im/actor/server/api/rpc/service/push/PushServiceImpl.scala @@ -9,8 +9,8 @@ import im.actor.api.rpc.encryption.ApiEncryptionKey import im.actor.api.rpc.misc.ResponseVoid import im.actor.api.rpc.push.PushService import im.actor.server.db.DbExtension -import im.actor.server.model.push.{ ActorPushCredentials, ApplePushCredentials, GooglePushCredentials } -import im.actor.server.persist.push.{ ActorPushCredentialsRepo, ApplePushCredentialsRepo, GooglePushCredentialsRepo } +import im.actor.server.model.push.{ ActorPushCredentials, ApplePushCredentials, FirebasePushCredentials, GCMPushCredentials } +import im.actor.server.persist.push.{ ActorPushCredentialsRepo, ApplePushCredentialsRepo, FirebasePushCredentialsKV, GooglePushCredentialsRepo } import im.actor.server.sequence.SeqUpdatesExtension import scodec.bits.BitVector import slick.driver.PostgresDriver.api._ @@ -27,20 +27,31 @@ final class PushServiceImpl( ) extends PushService { override implicit val ec: ExecutionContext = actorSystem.dispatcher - private implicit val db: Database = DbExtension(actorSystem).db + private val db = DbExtension(actorSystem).db private implicit val seqUpdExt: SeqUpdatesExtension = SeqUpdatesExtension(actorSystem) + private val firebaseKv = new FirebasePushCredentialsKV + private val OkVoid = Ok(ResponseVoid) private val ErrWrongToken = Error(PushRpcErrors.WrongToken) - override def doHandleRegisterGooglePush(projectId: Long, token: String, clientData: ClientData): Future[HandlerResult[ResponseVoid]] = { - val creds = GooglePushCredentials(clientData.authId, projectId, token) - val action: DBIO[HandlerResult[ResponseVoid]] = for { - _ ← GooglePushCredentialsRepo.deleteByToken(token) - _ ← GooglePushCredentialsRepo.createOrUpdate(creds) - } yield OkVoid - - db.run(action.transactionally) andThen { case _ ⇒ seqUpdExt.registerGooglePushCredentials(creds) } + override def doHandleRegisterGooglePush(projectId: Long, rawToken: String, clientData: ClientData): Future[HandlerResult[ResponseVoid]] = { + val (isFCM, token) = extractToken(rawToken) + if (isFCM) { + val creds = FirebasePushCredentials(clientData.authId, projectId, token) + for { + _ ← firebaseKv.deleteByToken(token) + _ ← firebaseKv.createOrUpdate(creds) + _ ← seqUpdExt.registerFirebasePushCredentials(creds) + } yield OkVoid + } else { + val creds = GCMPushCredentials(clientData.authId, projectId, token) + db.run(for { + _ ← GooglePushCredentialsRepo.deleteByToken(token) + _ ← GooglePushCredentialsRepo.createOrUpdate(creds) + _ ← DBIO.from(seqUpdExt.registerGCMPushCredentials(creds)) + } yield OkVoid) + } } override def doHandleRegisterApplePush(apnsKey: Int, token: String, clientData: ClientData): Future[HandlerResult[ResponseVoid]] = @@ -124,8 +135,14 @@ final class PushServiceImpl( } // TODO: figure out, should user be authorized? - override protected def doHandleUnregisterGooglePush(token: String, clientData: ClientData): Future[HandlerResult[ResponseVoid]] = - seqUpdExt.unregisterGooglePushCredentials(token) map (_ ⇒ OkVoid) + override protected def doHandleUnregisterGooglePush(rawToken: String, clientData: ClientData): Future[HandlerResult[ResponseVoid]] = { + val (isFCM, token) = extractToken(rawToken) + (if (isFCM) { + seqUpdExt.unregisterFirebasePushCredentials(token) + } else { + seqUpdExt.unregisterGCMPushCredentials(token) + }) map (_ ⇒ OkVoid) + } // TODO: figure out, should user be authorized? override protected def doHandleUnregisterActorPush(endpoint: String, clientData: ClientData): Future[HandlerResult[ResponseVoid]] = @@ -157,4 +174,14 @@ final class PushServiceImpl( FastFuture.successful(ErrWrongToken) } + // FIXME: temporary hack before schema has changed + private def extractToken(token: String): (Boolean, String) = { + val fcmPref = "FCM_" + if (token.startsWith(fcmPref)) { + true → token.drop(fcmPref.length) + } else { + false → token + } + } + } diff --git a/actor-server/actor-tests/src/test/scala/im/actor/server/api/rpc/service/auth/AuthServiceSpec.scala b/actor-server/actor-tests/src/test/scala/im/actor/server/api/rpc/service/auth/AuthServiceSpec.scala index 957e2abdc9..7f5c1dc89c 100644 --- a/actor-server/actor-tests/src/test/scala/im/actor/server/api/rpc/service/auth/AuthServiceSpec.scala +++ b/actor-server/actor-tests/src/test/scala/im/actor/server/api/rpc/service/auth/AuthServiceSpec.scala @@ -962,7 +962,7 @@ final class AuthServiceSpec val sessionId = createSessionId() implicit val clientData = ClientData(authId, sessionId, Some(AuthData(user.id, authSid, 42))) - seqUpdExt.registerGooglePushCredentials(model.push.GooglePushCredentials(authId, 22L, "hello")) + seqUpdExt.registerGCMPushCredentials(model.push.GCMPushCredentials(authId, 22L, "hello")) seqUpdExt.registerApplePushCredentials(model.push.ApplePushCredentials(authId, Some(Int32Value(22)), ByteString.copyFrom("hello".getBytes))) //let seqUpdateManager register credentials diff --git a/actor-server/src/linux/usr/share/actor/conf/server.conf.example b/actor-server/src/linux/usr/share/actor/conf/server.conf.example index a4b7e43ea8..7e6d0543ef 100644 --- a/actor-server/src/linux/usr/share/actor/conf/server.conf.example +++ b/actor-server/src/linux/usr/share/actor/conf/server.conf.example @@ -219,8 +219,17 @@ services { # scope: "https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile https://www.google.com/m8/feeds/" #} - # Android & Chrome push notifications - # push { + # Android push notifications via GCM and Firebase + # gcm { + # # Keys for push notifications in format + # keys = [ + # { + # project-id: + # key: + # } + # ] + # } + # firebase { # # Keys for push notifications in format # keys = [ # { From 60e3c2480068a97cc380442673348550702f2525 Mon Sep 17 00:00:00 2001 From: rockjam Date: Fri, 7 Oct 2016 01:55:52 +0300 Subject: [PATCH 384/414] fix(server): def -> val in config map --- .../src/main/scala/im/actor/server/push/google/configs.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/push/google/configs.scala b/actor-server/actor-core/src/main/scala/im/actor/server/push/google/configs.scala index d27dfbb048..36aff09dc6 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/push/google/configs.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/push/google/configs.scala @@ -9,7 +9,7 @@ import scala.util.Try private final case class GooglePushKey(projectId: Long, key: String) private[google] final case class GooglePushManagerConfig(keys: List[GooglePushKey]) { - def keyMap: Map[Long, String] = + val keyMap: Map[Long, String] = (keys map { case GooglePushKey(projectId, key) ⇒ projectId → key }).toMap From 239e0642847db7480b669f1c339267aed857364d Mon Sep 17 00:00:00 2001 From: rockjam Date: Fri, 7 Oct 2016 03:05:22 +0300 Subject: [PATCH 385/414] fix(server): catch config exception earlier --- .../src/main/scala/im/actor/server/push/google/configs.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/push/google/configs.scala b/actor-server/actor-core/src/main/scala/im/actor/server/push/google/configs.scala index 36aff09dc6..e717788d24 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/push/google/configs.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/push/google/configs.scala @@ -27,5 +27,5 @@ private[google] object GooglePushManagerConfig { def loadFirebase: Try[GooglePushManagerConfig] = load(ActorConfig.load().getConfig("services.google.firebase")) - private def load(config: Config) = Try(config.extract[GooglePushManagerConfig]) + private def load(config: => Config) = Try(config.extract[GooglePushManagerConfig]) } From 82fa6967263bf23071706db05d61f14b6e375e06 Mon Sep 17 00:00:00 2001 From: rockjam Date: Fri, 7 Oct 2016 04:07:28 +0300 Subject: [PATCH 386/414] fix(server): gcm config --- .../scala/im/actor/server/push/google/configs.scala | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/push/google/configs.scala b/actor-server/actor-core/src/main/scala/im/actor/server/push/google/configs.scala index e717788d24..a509251158 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/push/google/configs.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/push/google/configs.scala @@ -1,6 +1,6 @@ package im.actor.server.push.google -import com.typesafe.config.{ Config, ConfigException } +import com.typesafe.config.Config import com.github.kxbmap.configs.syntax._ import im.actor.config.ActorConfig @@ -19,13 +19,13 @@ private[google] object GooglePushManagerConfig { def loadGCM: Try[GooglePushManagerConfig] = { val config = ActorConfig.load() - load(config.getConfig("services.google.gcm")) recoverWith { - case e: ConfigException.Missing ⇒ load(config.getConfig("services.google.push")) // legacy conf, before firebase - } + val legacy = load(config.getConfig("services.google.push")) + val gcm = load(config.getConfig("services.google.gcm")) + legacy.orElse(gcm) } def loadFirebase: Try[GooglePushManagerConfig] = load(ActorConfig.load().getConfig("services.google.firebase")) - private def load(config: => Config) = Try(config.extract[GooglePushManagerConfig]) + private def load(config: ⇒ Config) = Try(config.extract[GooglePushManagerConfig]) } From c1ed5545e659c42d0ce6958587946a22950c9fd3 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Fri, 7 Oct 2016 14:14:43 +0300 Subject: [PATCH 387/414] fix(android): start audio record actor on resume --- .../inputbar/InputBarFragment.java | 63 ++++++++++--------- 1 file changed, 33 insertions(+), 30 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/inputbar/InputBarFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/inputbar/InputBarFragment.java index 45c1f9cab3..47a3ed8a3b 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/inputbar/InputBarFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/inputbar/InputBarFragment.java @@ -252,36 +252,6 @@ public void onShow() { return true; }); - voiceRecordActor = ActorSystem.system().actorOf(Props.create(() -> { - return new VoiceCaptureActor(getActivity(), new VoiceCaptureActor.VoiceCaptureCallback() { - @Override - public void onRecordProgress(final long time) { - getActivity().runOnUiThread(() -> { - audioTimer.setText(messenger().getFormatter().formatDuration((int) (time / 1000))); - }); - } - - @Override - public void onRecordCrash() { - getActivity().runOnUiThread(() -> { - hideAudio(true); - }); - } - - @Override - public void onRecordStop(long progress) { - if (progress < 1200) { - //Cancel - } else { - Fragment parent = getParentFragment(); - if (parent instanceof InputBarCallback) { - ((InputBarCallback) parent).onAudioSent((int) progress, audioFile); - } - } - } - }); - }).changeDispatcher("voice_capture_dispatcher"), "actor/voice_capture"); - return res; } @@ -584,6 +554,39 @@ public void onPause() { voiceRecordActor.send(PoisonPill.INSTANCE); } + @Override + public void onResume() { + super.onResume(); + voiceRecordActor = ActorSystem.system().actorOf(Props.create(() -> new VoiceCaptureActor(getActivity(), new VoiceCaptureActor.VoiceCaptureCallback() { + @Override + public void onRecordProgress(final long time) { + getActivity().runOnUiThread(() -> { + audioTimer.setText(messenger().getFormatter().formatDuration((int) (time / 1000))); + }); + } + + @Override + public void onRecordCrash() { + getActivity().runOnUiThread(() -> { + hideAudio(true); + }); + } + + @Override + public void onRecordStop(long progress) { + if (progress < 1200) { + //Cancel + } else { + Fragment parent = getParentFragment(); + if (parent instanceof InputBarCallback) { + ((InputBarCallback) parent).onAudioSent((int) progress, audioFile); + } + } + } + })).changeDispatcher("voice_capture_dispatcher"), "actor/voice_capture"); + + } + @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); From ab893a2157c7a7b2d4b3557d669f3681b4da952c Mon Sep 17 00:00:00 2001 From: kor_ka Date: Fri, 7 Oct 2016 23:07:06 +0300 Subject: [PATCH 388/414] wip(core+android): implement on client contacts privacy --- .../src/main/java/im/actor/Application.java | 1 + .../src/main/java/im/actor/sdk/ActorSDK.java | 12 + .../controllers/profile/ProfileFragment.java | 241 +++++++++--------- .../src/main/res/values/ui_text.xml | 3 + .../java/im/actor/core/Configuration.java | 13 + .../im/actor/core/ConfigurationBuilder.java | 15 ++ .../core/modules/contacts/ContactsModule.java | 12 + .../core/modules/users/router/UserRouter.java | 60 ++++- .../java/im/actor/core/viewmodel/UserVM.java | 14 + 9 files changed, 251 insertions(+), 120 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/Application.java b/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/Application.java index 9722be5652..d737d254b9 100644 --- a/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/Application.java +++ b/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/Application.java @@ -56,6 +56,7 @@ public void onCreate() { public void onConfigureActorSDK() { ActorSDK.sharedActor().setDelegate(new ActorSDKDelegate()); ActorSDK.sharedActor().setPushId(209133700967L); + ActorSDK.sharedActor().setOnClientPrivacyEnabled(true); ActorStyle style = ActorSDK.sharedActor().style; style.setDialogsActiveTextColor(0xff5882ac); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorSDK.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorSDK.java index 08bc1904aa..d6c6684a37 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorSDK.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorSDK.java @@ -194,6 +194,9 @@ public class ActorSDK { */ private boolean callsEnabled = false; private boolean videoCallsEnabled = false; + + private boolean onClientPrivacyEnabled = false; + private String inviteDataUrl = "https://api.actor.im/v1/groups/invites/"; private ActorSDK() { @@ -257,6 +260,7 @@ public void createActor(final Application application) { } builder.setPhoneBookProvider(new AndroidPhoneBook()); builder.setVideoCallsEnabled(videoCallsEnabled); + builder.setOnClientPrivacyEnabled(onClientPrivacyEnabled); builder.setNotificationProvider(new AndroidNotifications(application)); builder.setDeviceCategory(DeviceCategory.MOBILE); builder.setPlatformType(PlatformType.ANDROID); @@ -1051,6 +1055,14 @@ public void setVideoCallsEnabled(boolean videoCallsEnabled) { this.videoCallsEnabled = videoCallsEnabled; } + public void setOnClientPrivacyEnabled(boolean onClientPrivacyEnabled) { + this.onClientPrivacyEnabled = onClientPrivacyEnabled; + } + + public boolean isOnClientPrivacyEnabled() { + return onClientPrivacyEnabled; + } + public String getInviteDataUrl() { return inviteDataUrl; } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/profile/ProfileFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/profile/ProfileFragment.java index bf969b4827..97201e3b59 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/profile/ProfileFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/profile/ProfileFragment.java @@ -284,137 +284,142 @@ public void onChanged(final String newUserAbout, Value valueModel) { } }); - // - // Phones - // - for (int i = 0; i < phones.size(); i++) { - final UserPhone userPhone = phones.get(i); - - // Formatting Phone Number - String _phoneNumber; - try { - Phonenumber.PhoneNumber number = PhoneNumberUtil.getInstance().parse("+" + userPhone.getPhone(), "us"); - _phoneNumber = PhoneNumberUtil.getInstance().format(number, PhoneNumberUtil.PhoneNumberFormat.INTERNATIONAL); - } catch (NumberParseException e) { - e.printStackTrace(); - _phoneNumber = "+" + userPhone.getPhone(); - } - final String phoneNumber = _phoneNumber; + if (!ActorSDK.sharedActor().isOnClientPrivacyEnabled() || user.isInPhoneBook().get()) { + // + // Phones + // - String phoneTitle = userPhone.getTitle(); + for (int i = 0; i < phones.size(); i++) { + final UserPhone userPhone = phones.get(i); + + // Formatting Phone Number + String _phoneNumber; + try { + Phonenumber.PhoneNumber number = PhoneNumberUtil.getInstance().parse("+" + userPhone.getPhone(), "us"); + _phoneNumber = PhoneNumberUtil.getInstance().format(number, PhoneNumberUtil.PhoneNumberFormat.INTERNATIONAL); + } catch (NumberParseException e) { + e.printStackTrace(); + _phoneNumber = "+" + userPhone.getPhone(); + } + final String phoneNumber = _phoneNumber; - // "Mobile phone" is default value for non specified title - // Trying to localize this - if (phoneTitle.toLowerCase().equals("mobile phone")) { - phoneTitle = getString(R.string.settings_mobile_phone); - } - View view = buildRecord(phoneTitle, - phoneNumber, - R.drawable.ic_import_contacts_black_24dp, - isFirstContact, - false, - inflater, contactsContainer); - if (isFirstContact) { - recordFieldWithIcon = view; - } + String phoneTitle = userPhone.getTitle(); - view.setOnClickListener(v -> { - new AlertDialog.Builder(getActivity()) - .setItems(new CharSequence[]{ - getString(R.string.phone_menu_call).replace("{0}", phoneNumber), - getString(R.string.phone_menu_sms).replace("{0}", phoneNumber), - getString(R.string.phone_menu_share).replace("{0}", phoneNumber), - getString(R.string.phone_menu_copy) - }, (dialog, which) -> { - if (which == 0) { - startActivity(new Intent(Intent.ACTION_DIAL) - .setData(Uri.parse("tel:+" + userPhone.getPhone()))); - } else if (which == 1) { - startActivity(new Intent(Intent.ACTION_VIEW) - .setData(Uri.parse("sms:+" + userPhone.getPhone()))); - } else if (which == 2) { - startActivity(new Intent(Intent.ACTION_SEND) - .setType("text/plain") - .putExtra(Intent.EXTRA_TEXT, getString(R.string.settings_share_text) - .replace("{0}", phoneNumber) - .replace("{1}", user.getName().get()))); - } else if (which == 3) { - ClipboardManager clipboard = - (ClipboardManager) getActivity() - .getSystemService(Context.CLIPBOARD_SERVICE); - ClipData clip = ClipData.newPlainText("Phone number", phoneNumber); - clipboard.setPrimaryClip(clip); - Snackbar.make(res, R.string.toast_phone_copied, Snackbar.LENGTH_SHORT) - .show(); - } - }) - .show() - .setCanceledOnTouchOutside(true); - }); - - view.setOnLongClickListener(v -> { - ClipboardManager clipboard = (ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE); - ClipData clip = ClipData.newPlainText("Phone number", "+" + userPhone.getPhone()); - clipboard.setPrimaryClip(clip); - Snackbar.make(res, R.string.toast_phone_copied, Snackbar.LENGTH_SHORT) - .show(); - return true; - }); + // "Mobile phone" is default value for non specified title + // Trying to localize this + if (phoneTitle.toLowerCase().equals("mobile phone")) { + phoneTitle = getString(R.string.settings_mobile_phone); + } - isFirstContact = false; - } + View view = buildRecord(phoneTitle, + phoneNumber, + R.drawable.ic_import_contacts_black_24dp, + isFirstContact, + false, + inflater, contactsContainer); + if (isFirstContact) { + recordFieldWithIcon = view; + } - // - // Emails - // + view.setOnClickListener(v -> { + new AlertDialog.Builder(getActivity()) + .setItems(new CharSequence[]{ + getString(R.string.phone_menu_call).replace("{0}", phoneNumber), + getString(R.string.phone_menu_sms).replace("{0}", phoneNumber), + getString(R.string.phone_menu_share).replace("{0}", phoneNumber), + getString(R.string.phone_menu_copy) + }, (dialog, which) -> { + if (which == 0) { + startActivity(new Intent(Intent.ACTION_DIAL) + .setData(Uri.parse("tel:+" + userPhone.getPhone()))); + } else if (which == 1) { + startActivity(new Intent(Intent.ACTION_VIEW) + .setData(Uri.parse("sms:+" + userPhone.getPhone()))); + } else if (which == 2) { + startActivity(new Intent(Intent.ACTION_SEND) + .setType("text/plain") + .putExtra(Intent.EXTRA_TEXT, getString(R.string.settings_share_text) + .replace("{0}", phoneNumber) + .replace("{1}", user.getName().get()))); + } else if (which == 3) { + ClipboardManager clipboard = + (ClipboardManager) getActivity() + .getSystemService(Context.CLIPBOARD_SERVICE); + ClipData clip = ClipData.newPlainText("Phone number", phoneNumber); + clipboard.setPrimaryClip(clip); + Snackbar.make(res, R.string.toast_phone_copied, Snackbar.LENGTH_SHORT) + .show(); + } + }) + .show() + .setCanceledOnTouchOutside(true); + }); + + view.setOnLongClickListener(v -> { + ClipboardManager clipboard = (ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE); + ClipData clip = ClipData.newPlainText("Phone number", "+" + userPhone.getPhone()); + clipboard.setPrimaryClip(clip); + Snackbar.make(res, R.string.toast_phone_copied, Snackbar.LENGTH_SHORT) + .show(); + return true; + }); - for (int i = 0; i < emails.size(); i++) { - final UserEmail userEmail = emails.get(i); - View view = buildRecord(userEmail.getTitle(), - userEmail.getEmail(), - R.drawable.ic_import_contacts_black_24dp, - isFirstContact, - false, - inflater, contactsContainer); - if (isFirstContact) { - recordFieldWithIcon = view; + isFirstContact = false; } - view.setOnClickListener(v -> { - new AlertDialog.Builder(getActivity()) - .setItems(new CharSequence[]{ - getString(R.string.email_menu_email).replace("{0}", userEmail.getEmail()), - getString(R.string.phone_menu_copy) - }, (dialog, which) -> { - if (which == 0) { - startActivity(new Intent(Intent.ACTION_SENDTO, Uri.fromParts("mailto", userEmail.getEmail(), null))); - } else if (which == 1) { - ClipboardManager clipboard = - (ClipboardManager) getActivity() - .getSystemService(Context.CLIPBOARD_SERVICE); - ClipData clip = ClipData.newPlainText("Email", userEmail.getEmail()); - clipboard.setPrimaryClip(clip); - Snackbar.make(res, R.string.toast_email_copied, Snackbar.LENGTH_SHORT) - .show(); - } - }) - .show() - .setCanceledOnTouchOutside(true); - }); - view.setOnLongClickListener(v -> { - ClipboardManager clipboard = (ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE); - ClipData clip = ClipData.newPlainText("Email", "+" + userEmail.getEmail()); - clipboard.setPrimaryClip(clip); - Snackbar.make(res, R.string.toast_email_copied, Snackbar.LENGTH_SHORT) - .show(); - return true; - }); - isFirstContact = false; + // + // Emails + // + + for (int i = 0; i < emails.size(); i++) { + final UserEmail userEmail = emails.get(i); + View view = buildRecord(userEmail.getTitle(), + userEmail.getEmail(), + R.drawable.ic_import_contacts_black_24dp, + isFirstContact, + false, + inflater, contactsContainer); + if (isFirstContact) { + recordFieldWithIcon = view; + } + + view.setOnClickListener(v -> { + new AlertDialog.Builder(getActivity()) + .setItems(new CharSequence[]{ + getString(R.string.email_menu_email).replace("{0}", userEmail.getEmail()), + getString(R.string.phone_menu_copy) + }, (dialog, which) -> { + if (which == 0) { + startActivity(new Intent(Intent.ACTION_SENDTO, Uri.fromParts("mailto", userEmail.getEmail(), null))); + } else if (which == 1) { + ClipboardManager clipboard = + (ClipboardManager) getActivity() + .getSystemService(Context.CLIPBOARD_SERVICE); + ClipData clip = ClipData.newPlainText("Email", userEmail.getEmail()); + clipboard.setPrimaryClip(clip); + Snackbar.make(res, R.string.toast_email_copied, Snackbar.LENGTH_SHORT) + .show(); + } + }) + .show() + .setCanceledOnTouchOutside(true); + }); + view.setOnLongClickListener(v -> { + ClipboardManager clipboard = (ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE); + ClipData clip = ClipData.newPlainText("Email", "+" + userEmail.getEmail()); + clipboard.setPrimaryClip(clip); + Snackbar.make(res, R.string.toast_email_copied, Snackbar.LENGTH_SHORT) + .show(); + return true; + }); + isFirstContact = false; + } } + // // Username // diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml index f7c17c7e06..7ff38a5215 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml @@ -480,6 +480,9 @@ Unable to revoke invite link Loading invite link + JOIN + OPEN + Join "%1$s"? diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Configuration.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Configuration.java index 665496feaa..ddd67f9317 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Configuration.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Configuration.java @@ -56,6 +56,8 @@ public class Configuration { @Property("readonly, nonatomic") private final boolean enablePhoneBookImport; @Property("readonly, nonatomic") + private final boolean enableOnClientPrivacy; + @Property("readonly, nonatomic") private final CallsProvider callsProvider; @Property("readonly, nonatomic") private final RawUpdatesHandler rawUpdatesHandler; @@ -83,6 +85,7 @@ public class Configuration { String customAppName, TrustedKey[] trustedKeys, boolean enablePhoneBookImport, + boolean enableOnClientPrivcy, CallsProvider callsProvider, RawUpdatesHandler rawUpdatesHandler, boolean voiceCallsEnabled, @@ -107,6 +110,7 @@ public class Configuration { this.customAppName = customAppName; this.trustedKeys = trustedKeys; this.enablePhoneBookImport = enablePhoneBookImport; + this.enableOnClientPrivacy = enableOnClientPrivcy; this.callsProvider = callsProvider; this.rawUpdatesHandler = rawUpdatesHandler; this.voiceCallsEnabled = voiceCallsEnabled; @@ -161,6 +165,15 @@ public boolean isEnablePhoneBookImport() { return enablePhoneBookImport; } + /** + * Getting if app check if contact not in phone book and hides phone/email in that case + * + * @return if on client privacy enabled + */ + public boolean isEnableOnClientPrivacy() { + return enableOnClientPrivacy; + } + /** * Getting Trusted keys * diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/ConfigurationBuilder.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/ConfigurationBuilder.java index 3942b86857..0f82c000c7 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/ConfigurationBuilder.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/ConfigurationBuilder.java @@ -58,6 +58,7 @@ public class ConfigurationBuilder { private String customAppName; private boolean isPhoneBookImportEnabled = true; + private boolean isOnClientPrivacyEnabled = false; private CallsProvider callsProvider; private RawUpdatesHandler rawUpdatesHandler; @@ -129,6 +130,19 @@ public ConfigurationBuilder setPhoneBookImportEnabled(boolean isPhoneBookImportE return this; } + /** + * Setting if application uses on client contacts privacy + * + * @param isOnClientPrivacyEnabled enabled flag + * @return this + */ + @NotNull + @ObjectiveCName("setOnClientPrivacyEnabled:") + public ConfigurationBuilder setOnClientPrivacyEnabled(boolean isOnClientPrivacyEnabled) { + this.isOnClientPrivacyEnabled = isOnClientPrivacyEnabled; + return this; + } + /** * Setting Web RTC support provider * @@ -419,6 +433,7 @@ public Configuration build() { customAppName, trustedKeys.toArray(new TrustedKey[trustedKeys.size()]), isPhoneBookImportEnabled, + isOnClientPrivacyEnabled, callsProvider, rawUpdatesHandler, voiceCallsEnabled, diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/contacts/ContactsModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/contacts/ContactsModule.java index 723f519cf6..4a7af1c6b9 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/contacts/ContactsModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/contacts/ContactsModule.java @@ -86,6 +86,18 @@ public boolean isUserContact(int uid) { return preferences().getBool("contact_" + uid, false); } + public void markInPhoneBook(int uid) { + preferences().putBool("contact_in_pb_" + uid, true); + } + + public void markNotInPhoneBook(int uid) { + preferences().putBool("contact_in_pb_" + uid, false); + } + + public boolean isUserInPhoneBook(int uid) { + return preferences().getBool("contact_in_pb_" + uid, false); + } + public Promise findUsers(final String query) { return api(new RequestSearchContacts(query, ApiSupportConfiguration.OPTIMIZATIONS)) .chain(responseSearchContacts -> updates().loadRequiredPeers(responseSearchContacts.getUserPeers(), new ArrayList<>())) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/users/router/UserRouter.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/users/router/UserRouter.java index fb25bd04af..7615243210 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/users/router/UserRouter.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/users/router/UserRouter.java @@ -34,11 +34,17 @@ import im.actor.core.modules.ModuleActor; import im.actor.core.modules.ModuleContext; import im.actor.core.modules.contacts.ContactsSyncActor; +import im.actor.core.modules.contacts.entity.BookImportStorage; import im.actor.core.modules.users.router.entity.RouterApplyUsers; import im.actor.core.modules.users.router.entity.RouterFetchMissingUsers; import im.actor.core.modules.users.router.entity.RouterLoadFullUser; import im.actor.core.modules.users.router.entity.RouterUserUpdate; import im.actor.core.network.parser.Update; +import im.actor.core.viewmodel.UserEmail; +import im.actor.core.viewmodel.UserPhone; +import im.actor.core.viewmodel.UserVM; +import im.actor.core.viewmodel.generics.ArrayListUserEmail; +import im.actor.core.viewmodel.generics.ArrayListUserPhone; import im.actor.runtime.actors.messages.Void; import im.actor.runtime.annotations.Verified; import im.actor.runtime.function.Function; @@ -60,7 +66,6 @@ public UserRouter(ModuleContext context) { super(context); } - // // Small User // @@ -380,7 +385,11 @@ private void onLoadFullUser(int uid) { // Updating user in collection users().addOrUpdateItem(upd); }) - .after((r, e) -> unfreeze()); + .after((r, e) -> unfreeze()) + .chain(r -> { + checkIsInPhoneBook(getUserVM(uid)); + return Promise.success(null); + }); } @Verified @@ -426,6 +435,53 @@ public Promise> apply(ApiUser u) { .after((r, e) -> unfreeze()); } + private BookImportStorage getBookImportStorage() { + BookImportStorage storage = null; + byte[] data = context().getContactsModule().getBookImportState().get(0); + if (data != null) { + try { + storage = new BookImportStorage(data); + } catch (Exception e) { + e.getLocalizedMessage(); + } + } + return storage; + } + + protected void checkIsInPhoneBook(UserVM userVM) { + if (!config().isEnableOnClientPrivacy()) { + return; + } + + BookImportStorage storage = getBookImportStorage(); + if (storage == null) { + return; + } + + ArrayListUserPhone userPhones = userVM.getPhones().get(); + ArrayListUserEmail userEmails = userVM.getEmails().get(); + + if (!userVM.isInPhoneBook().get()) { + for (UserPhone phone : userPhones) { + if (storage.isImported(phone.getPhone())) { + userVM.isInPhoneBook().change(true); + context().getContactsModule().markInPhoneBook(userVM.getId()); + break; + } + } + } + + if (!userVM.isInPhoneBook().get()) { + for (UserEmail email : userEmails) { + if (storage.isImported(email.getEmail())) { + userVM.isInPhoneBook().change(true); + context().getContactsModule().markInPhoneBook(userVM.getId()); + break; + } + } + } + } + // // Tools diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/UserVM.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/UserVM.java index bd7b823406..8752651a88 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/UserVM.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/UserVM.java @@ -69,6 +69,8 @@ public static ValueModelCreator CREATOR(final ModuleContext module @NotNull private BooleanValueModel isContact; @NotNull + private BooleanValueModel isInPhoneBook; + @NotNull private BooleanValueModel isBlocked; @NotNull private BooleanValueModel isVerified; @@ -112,6 +114,7 @@ public UserVM(@NotNull User user, @NotNull ModuleContext modules) { about = new StringValueModel("user." + id + ".about", user.getAbout()); avatar = new AvatarValueModel("user." + id + ".avatar", user.getAvatar()); isContact = new BooleanValueModel("user." + id + ".contact", modules.getContactsModule().isUserContact(id)); + isInPhoneBook = new BooleanValueModel("user." + id + ".in_pb", modules.getContactsModule().isUserInPhoneBook(id)); isBlocked = new BooleanValueModel("user." + id + ".blocked", user.isBlocked()); isVerified = new BooleanValueModel("user." + id + ".is_verified", user.isVerified()); timeZone = new StringValueModel("user." + id + ".time_zone", user.getTimeZone()); @@ -264,6 +267,17 @@ public BooleanValueModel isContact() { return isContact; } + /** + * Get ValueModel of flag if user is in phone book + * + * @return ValueModel of Boolean + */ + @NotNull + @ObjectiveCName("isInPhoneBookModel") + public BooleanValueModel isInPhoneBook() { + return isInPhoneBook; + } + /** * Get ValueModel of flag if user is blocked * From 71e5ef2b5cdbf19ad2316e81e0b35523914cdfe9 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Fri, 7 Oct 2016 23:45:49 +0300 Subject: [PATCH 389/414] wip(core+android): implement on client contacts privacy --- .../core/modules/users/router/UserRouter.java | 95 +++++++++++-------- 1 file changed, 58 insertions(+), 37 deletions(-) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/users/router/UserRouter.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/users/router/UserRouter.java index 7615243210..acc247e6e7 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/users/router/UserRouter.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/users/router/UserRouter.java @@ -29,6 +29,9 @@ import im.actor.core.entity.Message; import im.actor.core.entity.MessageState; import im.actor.core.entity.Peer; +import im.actor.core.entity.PhoneBookContact; +import im.actor.core.entity.PhoneBookEmail; +import im.actor.core.entity.PhoneBookPhone; import im.actor.core.entity.User; import im.actor.core.entity.content.ServiceUserRegistered; import im.actor.core.modules.ModuleActor; @@ -40,6 +43,7 @@ import im.actor.core.modules.users.router.entity.RouterLoadFullUser; import im.actor.core.modules.users.router.entity.RouterUserUpdate; import im.actor.core.network.parser.Update; +import im.actor.core.providers.PhoneBookProvider; import im.actor.core.viewmodel.UserEmail; import im.actor.core.viewmodel.UserPhone; import im.actor.core.viewmodel.UserVM; @@ -62,6 +66,9 @@ public class UserRouter extends ModuleActor { private HashSet requestedFullUsers = new HashSet<>(); private boolean isFreezed = false; + PhoneBookProvider phoneBookProvider = config().getPhoneBookProvider(); + List contacts = null; + public UserRouter(ModuleContext context) { super(context); } @@ -385,11 +392,8 @@ private void onLoadFullUser(int uid) { // Updating user in collection users().addOrUpdateItem(upd); }) - .after((r, e) -> unfreeze()) - .chain(r -> { - checkIsInPhoneBook(getUserVM(uid)); - return Promise.success(null); - }); + .chain(r -> checkIsInPhoneBook(getUserVM(uid))) + .after((r, e) -> unfreeze()); } @Verified @@ -435,51 +439,68 @@ public Promise> apply(ApiUser u) { .after((r, e) -> unfreeze()); } - private BookImportStorage getBookImportStorage() { - BookImportStorage storage = null; - byte[] data = context().getContactsModule().getBookImportState().get(0); - if (data != null) { - try { - storage = new BookImportStorage(data); - } catch (Exception e) { - e.getLocalizedMessage(); - } + private Promise> getPhoneBook() { + if (contacts == null) { + return new Promise>(resolver -> { + phoneBookProvider.loadPhoneBook(contacts1 -> { + contacts = contacts1; + resolver.result(contacts1); + }); + }); + } else { + return Promise.success(contacts); } - return storage; } - protected void checkIsInPhoneBook(UserVM userVM) { + protected Promise checkIsInPhoneBook(UserVM userVM) { + if (!config().isEnableOnClientPrivacy()) { - return; + return Promise.success(null); } - BookImportStorage storage = getBookImportStorage(); - if (storage == null) { - return; - } + return getPhoneBook().flatMap(phoneBookContacts -> new Promise(resolver -> { + ArrayListUserPhone userPhones = userVM.getPhones().get(); + ArrayListUserEmail userEmails = userVM.getEmails().get(); + + if (!userVM.isInPhoneBook().get()) { + outer: + for (UserPhone phone : userPhones) { - ArrayListUserPhone userPhones = userVM.getPhones().get(); - ArrayListUserEmail userEmails = userVM.getEmails().get(); + for (PhoneBookContact phoneBookContact : phoneBookContacts) { + + for (PhoneBookPhone phone1 : phoneBookContact.getPhones()) { + if (phone.getPhone() == phone1.getNumber()) { + userVM.isInPhoneBook().change(true); + context().getContactsModule().markInPhoneBook(userVM.getId()); + break outer; + } + } + } - if (!userVM.isInPhoneBook().get()) { - for (UserPhone phone : userPhones) { - if (storage.isImported(phone.getPhone())) { - userVM.isInPhoneBook().change(true); - context().getContactsModule().markInPhoneBook(userVM.getId()); - break; } } - } - if (!userVM.isInPhoneBook().get()) { - for (UserEmail email : userEmails) { - if (storage.isImported(email.getEmail())) { - userVM.isInPhoneBook().change(true); - context().getContactsModule().markInPhoneBook(userVM.getId()); - break; + if (!userVM.isInPhoneBook().get()) { + outer: + for (UserEmail email : userEmails) { + + for (PhoneBookContact phoneBookContact : phoneBookContacts) { + + for (PhoneBookEmail email1 : phoneBookContact.getEmails()) { + if (email.getEmail().equals(email1.getEmail())) { + userVM.isInPhoneBook().change(true); + context().getContactsModule().markInPhoneBook(userVM.getId()); + break outer; + } + } + } + } } - } + + resolver.result(null); + })); + } From 81326e240e5e402d1c0b4cb3198c09cf475e1213 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Mon, 10 Oct 2016 16:37:10 +0300 Subject: [PATCH 390/414] feat(core+android): implement on client contacts privacy --- .../core/modules/users/router/UserRouter.java | 66 +++++++++++-------- 1 file changed, 39 insertions(+), 27 deletions(-) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/users/router/UserRouter.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/users/router/UserRouter.java index acc247e6e7..04e0a139b6 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/users/router/UserRouter.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/users/router/UserRouter.java @@ -26,6 +26,8 @@ import im.actor.core.api.updates.UpdateUserPreferredLanguagesChanged; import im.actor.core.api.updates.UpdateUserTimeZoneChanged; import im.actor.core.api.updates.UpdateUserUnblocked; +import im.actor.core.entity.ContactRecord; +import im.actor.core.entity.ContactRecordType; import im.actor.core.entity.Message; import im.actor.core.entity.MessageState; import im.actor.core.entity.Peer; @@ -49,6 +51,7 @@ import im.actor.core.viewmodel.UserVM; import im.actor.core.viewmodel.generics.ArrayListUserEmail; import im.actor.core.viewmodel.generics.ArrayListUserPhone; +import im.actor.runtime.Log; import im.actor.runtime.actors.messages.Void; import im.actor.runtime.annotations.Verified; import im.actor.runtime.function.Function; @@ -381,7 +384,13 @@ private void onLoadFullUser(int uid) { .map(responseLoadFullUsers -> new Tuple2<>(responseLoadFullUsers, u)); } else { - return Promise.failure(new RuntimeException("Already loaded")); + //user already loaded, only perform is in phone book check + if (!getUserVM(uid).isInPhoneBook().get()) { + return checkIsInPhoneBook(u).flatMap(aVoid -> Promise.failure(new RuntimeException("Already loaded"))); + } else { + return Promise.failure(new RuntimeException("Already loaded")); + } + } }) .then(r -> { @@ -392,7 +401,7 @@ private void onLoadFullUser(int uid) { // Updating user in collection users().addOrUpdateItem(upd); }) - .chain(r -> checkIsInPhoneBook(getUserVM(uid))) + .chain(r -> checkIsInPhoneBook(r.getT2().updateExt(r.getT1().getFullUsers().get(0)))) .after((r, e) -> unfreeze()); } @@ -452,52 +461,55 @@ private Promise> getPhoneBook() { } } - protected Promise checkIsInPhoneBook(UserVM userVM) { + protected Promise checkIsInPhoneBook(User user) { if (!config().isEnableOnClientPrivacy()) { return Promise.success(null); } + Log.d("ON_CLIENT_PRIVACY", "checking " + user.getName() + " is in phone book"); + return getPhoneBook().flatMap(phoneBookContacts -> new Promise(resolver -> { - ArrayListUserPhone userPhones = userVM.getPhones().get(); - ArrayListUserEmail userEmails = userVM.getEmails().get(); + List userRecords = user.getRecords(); + + Log.d("ON_CLIENT_PRIVACY", "phonebook have " + phoneBookContacts.size() + " records"); + Log.d("ON_CLIENT_PRIVACY", "user have " + userRecords.size() + " records"); - if (!userVM.isInPhoneBook().get()) { - outer: - for (UserPhone phone : userPhones) { + outer: + for (ContactRecord record : userRecords) { - for (PhoneBookContact phoneBookContact : phoneBookContacts) { + for (PhoneBookContact phoneBookContact : phoneBookContacts) { - for (PhoneBookPhone phone1 : phoneBookContact.getPhones()) { - if (phone.getPhone() == phone1.getNumber()) { - userVM.isInPhoneBook().change(true); - context().getContactsModule().markInPhoneBook(userVM.getId()); + for (PhoneBookPhone phone1 : phoneBookContact.getPhones()) { + if (record.getRecordType() == ContactRecordType.PHONE) { + if (record.getRecordData().equals(phone1.getNumber() + "")) { + context().getContactsModule().markInPhoneBook(user.getUid()); + getUserVM(user.getUid()).isInPhoneBook().change(true); + Log.d("ON_CLIENT_PRIVACY", "in record book!"); break outer; } } - } - - } - } - if (!userVM.isInPhoneBook().get()) { - outer: - for (UserEmail email : userEmails) { - - for (PhoneBookContact phoneBookContact : phoneBookContacts) { + } - for (PhoneBookEmail email1 : phoneBookContact.getEmails()) { - if (email.getEmail().equals(email1.getEmail())) { - userVM.isInPhoneBook().change(true); - context().getContactsModule().markInPhoneBook(userVM.getId()); + for (PhoneBookEmail email : phoneBookContact.getEmails()) { + if (record.getRecordType() == ContactRecordType.EMAIL) { + if (record.getRecordData().equals(email.getEmail())) { + context().getContactsModule().markInPhoneBook(user.getUid()); + getUserVM(user.getUid()).isInPhoneBook().change(true); + Log.d("ON_CLIENT_PRIVACY", "in record book!"); break outer; } } - } + } } + } + Log.d("ON_CLIENT_PRIVACY", "finish check"); + + resolver.result(null); })); From 5a9677810bffca07601666991551c45a83a72748 Mon Sep 17 00:00:00 2001 From: rockjam Date: Mon, 10 Oct 2016 20:51:24 +0300 Subject: [PATCH 391/414] fix(server:push): use _authId in android notification --- .../push/google/GooglePushProvider.scala | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/push/google/GooglePushProvider.scala b/actor-server/actor-core/src/main/scala/im/actor/server/push/google/GooglePushProvider.scala index ebcd5bfafe..7d71d9eac3 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/push/google/GooglePushProvider.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/push/google/GooglePushProvider.scala @@ -13,7 +13,12 @@ final class GooglePushProvider(userId: Int, system: ActorSystem) extends PushPro val message = GooglePushMessage( to = creds.regId, collapse_key = Some(s"seq-invisible-${userId.toString}"), - data = Some(Map("seq" → seq.toString)), + data = Some( + Map( + "seq" → seq.toString, + "_authId" → creds.authId.toString + ) + ), time_to_live = None ) creds match { @@ -35,13 +40,16 @@ final class GooglePushProvider(userId: Int, system: ActorSystem) extends PushPro val message = GooglePushMessage( to = creds.regId, collapse_key = Some(s"seq-visible-${userId.toString}"), - data = Some(Map("seq" → seq.toString) ++ ( - data.text match { - case text if text.nonEmpty && isTextEnabled ⇒ - Map("message" → text) - case _ ⇒ Map.empty - } - )), + data = Some( + Map( + "seq" → seq.toString, + "_authId" → creds.authId.toString + ) ++ (data.text match { + case text if text.nonEmpty && isTextEnabled ⇒ + Map("message" → text) + case _ ⇒ Map.empty + }) + ), time_to_live = None ) creds match { From 2f94e003537f420c6485cc7daf3990cbe9bc3a98 Mon Sep 17 00:00:00 2001 From: rockjam Date: Mon, 10 Oct 2016 23:33:51 +0300 Subject: [PATCH 392/414] fix(server:push): group push notifications corrected --- .../src/main/protobuf/sequence.proto | 1 + .../actor/server/dialog/ActorDelivery.scala | 6 + .../im/actor/server/sequence/VendorPush.scala | 113 +++++++++++++----- .../operations/DeliveryOperations.scala | 15 ++- .../server/userconfig/SettingsKeys.scala | 9 +- 5 files changed, 105 insertions(+), 39 deletions(-) diff --git a/actor-server/actor-core/src/main/protobuf/sequence.proto b/actor-server/actor-core/src/main/protobuf/sequence.proto index d0c415f0a5..056b93a2f2 100644 --- a/actor-server/actor-core/src/main/protobuf/sequence.proto +++ b/actor-server/actor-core/src/main/protobuf/sequence.proto @@ -26,6 +26,7 @@ message PushData { string text = 1; string censoredText = 3; Peer peer = 2; + bool is_mentioned = 4; } message PushRules { diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/dialog/ActorDelivery.scala b/actor-server/actor-core/src/main/scala/im/actor/server/dialog/ActorDelivery.scala index f58eaaa387..d34c550516 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/dialog/ActorDelivery.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/dialog/ActorDelivery.scala @@ -40,6 +40,11 @@ final class ActorDelivery(val system: ActorSystem) quotedMessage = None ) + val isMentioned = message match { + case ApiTextMessage(_, mentions, _) ⇒ mentions.contains(receiverUserId) + case _ ⇒ false + } + for { senderName ← UserExtension(system).getName(senderUserId, receiverUserId) (pushText, censoredPushText) ← getPushText(peer, receiverUserId, senderName, message) @@ -51,6 +56,7 @@ final class ActorDelivery(val system: ActorSystem) .withText(pushText) .withCensoredText(censoredPushText) .withPeer(peer) + .withIsMentioned(isMentioned) ), deliveryId = seqUpdExt.msgDeliveryId(peer, randomId), deliveryTag = deliveryTag diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/sequence/VendorPush.scala b/actor-server/actor-core/src/main/scala/im/actor/server/sequence/VendorPush.scala index b940b8d6dc..08cfeb72b2 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/sequence/VendorPush.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/sequence/VendorPush.scala @@ -5,7 +5,7 @@ import akka.pattern.pipe import im.actor.concurrent.FutureExt import im.actor.server.db.DbExtension import im.actor.server.model.push._ -import im.actor.server.model.{ DeviceType, Peer } +import im.actor.server.model.{ DeviceType, Peer, PeerType } import im.actor.server.persist.{ AuthIdRepo, AuthSessionRepo } import im.actor.server.persist.configs.ParameterRepo import im.actor.server.persist.push.{ ActorPushCredentialsRepo, ApplePushCredentialsRepo, FirebasePushCredentialsKV, GooglePushCredentialsRepo } @@ -24,7 +24,13 @@ private final case class PushCredentialsInfo(appId: Int, authId: Long) private final case class AllNotificationSettings( generic: NotificationSettings = NotificationSettings(), - specific: Map[String, NotificationSettings] = Map.empty + specific: Map[String, NotificationSettings] = Map.empty, + groups: GroupNotificationSettings = GroupNotificationSettings() +) + +private final case class GroupNotificationSettings( + enabled: Boolean = true, + onlyMention: Boolean = false ) private final case class NotificationSettings( @@ -81,20 +87,29 @@ private final class SettingsControl(userId: Int) extends Actor with ActorLogging private def load(): Future[AllNotificationSettings] = db.run(for { - generic ← loadAction(DeviceType.Generic) - mobile ← loadAction(DeviceType.Mobile) - tablet ← loadAction(DeviceType.Tablet) - desktop ← loadAction(DeviceType.Desktop) + generic ← loadForDevice(DeviceType.Generic) + mobile ← loadForDevice(DeviceType.Mobile) + tablet ← loadForDevice(DeviceType.Tablet) + desktop ← loadForDevice(DeviceType.Desktop) + + groups ← loadForGroups() } yield AllNotificationSettings( generic = generic, specific = Map( DeviceType.Mobile → mobile, DeviceType.Tablet → tablet, DeviceType.Desktop → desktop - ) + ), + groups = groups )) - private def loadAction(deviceType: String): DBIO[NotificationSettings] = { + private def loadForGroups(): DBIO[GroupNotificationSettings] = + for { + enabled ← ParameterRepo.findBooleanValue(userId, SettingsKeys.accountGroupEnabled, true) + onlyMentions ← ParameterRepo.findBooleanValue(userId, SettingsKeys.accountGroupMentionEnabled, false) + } yield GroupNotificationSettings(enabled, onlyMentions) + + private def loadForDevice(deviceType: String): DBIO[NotificationSettings] = for { enabled ← ParameterRepo.findBooleanValue(userId, SettingsKeys.enabled(deviceType), true) sound ← ParameterRepo.findBooleanValue(userId, SettingsKeys.soundEnabled(deviceType), true) @@ -103,7 +118,7 @@ private final class SettingsControl(userId: Int) extends Actor with ActorLogging peers ← ParameterRepo.findPeerNotifications(userId, deviceType) customSounds ← ParameterRepo.findPeerRingtone(userId) } yield NotificationSettings(enabled, sound, vibration, text, customSounds.toMap, peers.toMap) - } + } private[sequence] final class VendorPush(userId: Int) extends Actor with ActorLogging with Stash { @@ -120,6 +135,7 @@ private[sequence] final class VendorPush(userId: Int) extends Actor with ActorLo private val applePushProvider = new ApplePushProvider(userId)(context.system) private val actorPushProvider = ActorPush(context.system) + // TODO: why do we need `PushCredentialsInfo`, we have `authId` anyway! private var mapping: Map[PushCredentials, PushCredentialsInfo] = Map.empty private var notificationSettings = AllNotificationSettings() @@ -214,33 +230,18 @@ private[sequence] final class VendorPush(userId: Int) extends Actor with ActorLo val deviceType = DeviceType(info.appId) if (rules.excludeAuthIds.contains(info.authId)) { - log.debug("AuthSid is excluded, not pushing") + log.debug("AuthId is excluded, not pushing") } else { rules.data match { case Some(data) ⇒ val settings = notificationSettings.specific.getOrElse(deviceType, notificationSettings.generic) - val isVisible = - (settings.enabled, data.peer) match { - case (true, Some(peer)) ⇒ - settings.peers.get(peer) match { - case Some(true) ⇒ - log.debug("Notifications for peer {} are enabled, push will be visible", peer) - true - case Some(false) ⇒ - log.debug("Notifications for peer {} are disabled, push will be invisible", peer) - false - case None ⇒ - log.debug("Notifications for peer {} are not set, push will be visible", peer) - true - } - case (true, None) ⇒ - log.debug("Notifications are enabled, delivering visible push") - true - case (false, _) ⇒ - log.debug("Notifications are disabled, delivering invisible push") - false - } + val isVisible = isNotificationVisible( + settings, + notificationSettings.groups, + data.peer, + data.isMentioned + ) if (isVisible) deliverVisible( @@ -262,6 +263,56 @@ private[sequence] final class VendorPush(userId: Int) extends Actor with ActorLo } } + private def isNotificationVisible( + settings: NotificationSettings, + groupSettings: GroupNotificationSettings, + optPeer: Option[Peer], + isMentioned: Boolean + ) = { + (settings.enabled, optPeer) match { + case (true, Some(peer)) ⇒ + peer.`type` match { + case PeerType.Group ⇒ + if (groupSettings.enabled) { + if (groupSettings.onlyMention) { + if (isMentioned) { + log.debug("User is mentioned, notification for group {} will be visible", peer) + true + } else { + log.debug("Message without mention, notification for group {} will be visible", peer) + false + } + } else { + log.debug("Group notifications are enabled, notification for group {} will be visible", peer) + true + } + } else { + log.debug("Group notifications are disabled, notification for group {} will be invisible", peer) + false + } + case _ ⇒ + settings.peers.get(peer) match { + case Some(true) ⇒ + log.debug("Notifications for peer {} are enabled, notification will be visible", peer) + true + case Some(false) ⇒ + log.debug("Notifications for peer {} are disabled, notification will be invisible", peer) + false + case None ⇒ + log.debug("Notifications for peer {} are not set, notification will be visible", peer) + true + } + + } + case (true, None) ⇒ + log.debug("Notifications are enabled, delivering visible push") + true + case (false, _) ⇒ + log.debug("Notifications are disabled, delivering invisible push") + false + } + } + /** * Delivers an invisible push with seq and contentAvailable * diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/sequence/operations/DeliveryOperations.scala b/actor-server/actor-core/src/main/scala/im/actor/server/sequence/operations/DeliveryOperations.scala index 04204cd210..b4d7d30e53 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/sequence/operations/DeliveryOperations.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/sequence/operations/DeliveryOperations.scala @@ -12,9 +12,11 @@ import scala.concurrent.Future trait DeliveryOperations { this: SeqUpdatesExtension ⇒ def pushRules(isFat: Boolean, pushText: Option[String], excludeAuthIds: Seq[Long] = Seq.empty): PushRules = - PushRules(isFat = isFat) - .withData(PushData().withText(pushText.getOrElse(""))) - .withExcludeAuthIds(excludeAuthIds) + PushRules( + isFat = isFat, + excludeAuthIds = excludeAuthIds, + data = pushText map (t ⇒ PushData(text = t)) + ) /** * Send update to all devices of user and return `SeqState` associated with `authId` @@ -147,9 +149,10 @@ trait DeliveryOperations { this: SeqUpdatesExtension ⇒ Future.sequence(userIds.toSeq map (deliverUpdate(_, deliver))) map (_ ⇒ ()) private def deliverUpdate(userId: Int, deliver: DeliverUpdate): Future[SeqState] = { - val isUpdateDefined = - deliver.getMapping.default.isDefined || deliver.getMapping.custom.nonEmpty - require(isUpdateDefined, "No default update nor authId-specific") + require( + deliver.getMapping.default.isDefined || deliver.getMapping.custom.nonEmpty, + "No default update nor authId-specific" + ) (region.ref ? Envelope(userId).withDeliverUpdate(deliver)).mapTo[SeqState] } diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/userconfig/SettingsKeys.scala b/actor-server/actor-core/src/main/scala/im/actor/server/userconfig/SettingsKeys.scala index 0f800020fc..a30578fa45 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/userconfig/SettingsKeys.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/userconfig/SettingsKeys.scala @@ -1,7 +1,5 @@ package im.actor.server.userconfig -import im.actor.server.model.{ Peer, PeerType } - object SettingsKeys { private def wrap(deviceType: String, postfix: String): String = s"category.$deviceType.notification.$postfix" @@ -9,6 +7,8 @@ object SettingsKeys { private def wrapEnabled(deviceType: String, postfix: String): String = s"category.$deviceType.notification.$postfix.enabled" + private def groups(postfix: String) = s"account.notifications.group.$postfix" + def enabled(deviceType: String) = wrapEnabled(deviceType) def soundEnabled(deviceType: String) = wrapEnabled(deviceType, "sound") @@ -16,4 +16,9 @@ object SettingsKeys { def vibrationEnabled(deviceType: String) = wrapEnabled(deviceType, "vibration") def textEnabled(deviceType: String) = wrap(deviceType, "show_text") + + def accountGroupEnabled = groups("enabled") + + def accountGroupMentionEnabled = groups("mentions") + } From 0b2c8e669bc1965b43928a7603e9bb6a8d68d453 Mon Sep 17 00:00:00 2001 From: rockjam Date: Tue, 11 Oct 2016 03:20:17 +0300 Subject: [PATCH 393/414] fix(server): bring badge back to ios push --- .../im/actor/server/push/apple/APNSSend.scala | 1 - .../server/push/apple/ApplePushProvider.scala | 27 ++++++++++--------- .../apple}/PushFutureListener.scala | 3 ++- 3 files changed, 17 insertions(+), 14 deletions(-) rename actor-server/actor-core/src/main/scala/im/actor/server/{sequence => push/apple}/PushFutureListener.scala (95%) diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/push/apple/APNSSend.scala b/actor-server/actor-core/src/main/scala/im/actor/server/push/apple/APNSSend.scala index 8ae184cb3d..825b83fbe3 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/push/apple/APNSSend.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/push/apple/APNSSend.scala @@ -5,7 +5,6 @@ import com.google.protobuf.wrappers.{ Int32Value, StringValue } import com.relayrides.pushy.apns.PushNotificationResponse import com.relayrides.pushy.apns.util.{ SimpleApnsPushNotification, TokenUtil } import im.actor.server.model.push.ApplePushCredentials -import im.actor.server.sequence.PushFutureListener import io.netty.util.concurrent.{ Future ⇒ NFuture } import scodec.bits.BitVector diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/push/apple/ApplePushProvider.scala b/actor-server/actor-core/src/main/scala/im/actor/server/push/apple/ApplePushProvider.scala index a4ffdd5441..135eebb2f2 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/push/apple/ApplePushProvider.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/push/apple/ApplePushProvider.scala @@ -45,21 +45,24 @@ final class ApplePushProvider(userId: Int)(implicit system: ActorSystem) extends ): Unit = { withClient(creds) { implicit client ⇒ if (isLegacyCreds(creds)) { - val builder = - new ApnsPayloadBuilder() - .addCustomProperty("seq", seq) - .setContentAvailable(true) + dialogExt.getUnreadTotal(userId) foreach { total ⇒ + val builder = + new ApnsPayloadBuilder() + .addCustomProperty("seq", seq) + .setContentAvailable(true) + .setBadgeNumber(total) - if (data.text.nonEmpty && isTextEnabled) - builder.setAlertBody(data.text) - else if (data.censoredText.nonEmpty) - builder.setAlertBody(data.censoredText) + if (data.text.nonEmpty && isTextEnabled) + builder.setAlertBody(data.text) + else if (data.censoredText.nonEmpty) + builder.setAlertBody(data.censoredText) - if (isSoundEnabled) - builder.setSoundFileName(customSound getOrElse "iapetus.caf") + if (isSoundEnabled) + builder.setSoundFileName(customSound getOrElse "iapetus.caf") - val payload = builder.buildWithDefaultMaximumLength() - sendNotification(payload, creds, userId) + val payload = builder.buildWithDefaultMaximumLength() + sendNotification(payload, creds, userId) + } } else { sendNotification(payload = seqOnly(seq), creds, userId) } diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/sequence/PushFutureListener.scala b/actor-server/actor-core/src/main/scala/im/actor/server/push/apple/PushFutureListener.scala similarity index 95% rename from actor-server/actor-core/src/main/scala/im/actor/server/sequence/PushFutureListener.scala rename to actor-server/actor-core/src/main/scala/im/actor/server/push/apple/PushFutureListener.scala index 5ad356be26..58e89e02cc 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/sequence/PushFutureListener.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/push/apple/PushFutureListener.scala @@ -1,10 +1,11 @@ -package im.actor.server.sequence +package im.actor.server.push.apple import akka.actor.ActorSystem import akka.event.Logging import com.relayrides.pushy.apns.PushNotificationResponse import com.relayrides.pushy.apns.util.SimpleApnsPushNotification import im.actor.server.model.push.ApplePushCredentials +import im.actor.server.sequence.SeqUpdatesExtension import im.actor.util.log.AnyRefLogSource import io.netty.util.concurrent.{ Future, GenericFutureListener } import scodec.bits.BitVector From d39d82045abe49605ef177eb893e036c31e5e032 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Tue, 11 Oct 2016 15:54:53 +0300 Subject: [PATCH 394/414] chore(core): trying to workaround j2objc flatmap bug --- .../core/modules/users/router/UserRouter.java | 72 +++++++++++-------- 1 file changed, 42 insertions(+), 30 deletions(-) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/users/router/UserRouter.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/users/router/UserRouter.java index 04e0a139b6..b54b8085cb 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/users/router/UserRouter.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/users/router/UserRouter.java @@ -386,7 +386,12 @@ private void onLoadFullUser(int uid) { } else { //user already loaded, only perform is in phone book check if (!getUserVM(uid).isInPhoneBook().get()) { - return checkIsInPhoneBook(u).flatMap(aVoid -> Promise.failure(new RuntimeException("Already loaded"))); + return checkIsInPhoneBook(u).flatMap(new Function>>() { + @Override + public Promise> apply(Void aVoid) { + return Promise.failure(new RuntimeException("Already loaded")); + } + }); } else { return Promise.failure(new RuntimeException("Already loaded")); } @@ -448,6 +453,7 @@ public Promise> apply(ApiUser u) { .after((r, e) -> unfreeze()); } + @Verified private Promise> getPhoneBook() { if (contacts == null) { return new Promise>(resolver -> { @@ -461,6 +467,7 @@ private Promise> getPhoneBook() { } } + @Verified protected Promise checkIsInPhoneBook(User user) { if (!config().isEnableOnClientPrivacy()) { @@ -469,49 +476,54 @@ protected Promise checkIsInPhoneBook(User user) { Log.d("ON_CLIENT_PRIVACY", "checking " + user.getName() + " is in phone book"); - return getPhoneBook().flatMap(phoneBookContacts -> new Promise(resolver -> { - List userRecords = user.getRecords(); + return getPhoneBook().flatMap(new Function, Promise>() { + @Override + public Promise apply(List phoneBookContacts) { + return new Promise(resolver -> { + List userRecords = user.getRecords(); + + Log.d("ON_CLIENT_PRIVACY", "phonebook have " + phoneBookContacts.size() + " records"); + Log.d("ON_CLIENT_PRIVACY", "user have " + userRecords.size() + " records"); - Log.d("ON_CLIENT_PRIVACY", "phonebook have " + phoneBookContacts.size() + " records"); - Log.d("ON_CLIENT_PRIVACY", "user have " + userRecords.size() + " records"); + outer: + for (ContactRecord record : userRecords) { - outer: - for (ContactRecord record : userRecords) { + for (PhoneBookContact phoneBookContact : phoneBookContacts) { - for (PhoneBookContact phoneBookContact : phoneBookContacts) { + for (PhoneBookPhone phone1 : phoneBookContact.getPhones()) { + if (record.getRecordType() == ContactRecordType.PHONE) { + if (record.getRecordData().equals(phone1.getNumber() + "")) { + context().getContactsModule().markInPhoneBook(user.getUid()); + getUserVM(user.getUid()).isInPhoneBook().change(true); + Log.d("ON_CLIENT_PRIVACY", "in record book!"); + break outer; + } + } - for (PhoneBookPhone phone1 : phoneBookContact.getPhones()) { - if (record.getRecordType() == ContactRecordType.PHONE) { - if (record.getRecordData().equals(phone1.getNumber() + "")) { - context().getContactsModule().markInPhoneBook(user.getUid()); - getUserVM(user.getUid()).isInPhoneBook().change(true); - Log.d("ON_CLIENT_PRIVACY", "in record book!"); - break outer; } - } - } + for (PhoneBookEmail email : phoneBookContact.getEmails()) { + if (record.getRecordType() == ContactRecordType.EMAIL) { + if (record.getRecordData().equals(email.getEmail())) { + context().getContactsModule().markInPhoneBook(user.getUid()); + getUserVM(user.getUid()).isInPhoneBook().change(true); + Log.d("ON_CLIENT_PRIVACY", "in record book!"); + break outer; + } + } - for (PhoneBookEmail email : phoneBookContact.getEmails()) { - if (record.getRecordType() == ContactRecordType.EMAIL) { - if (record.getRecordData().equals(email.getEmail())) { - context().getContactsModule().markInPhoneBook(user.getUid()); - getUserVM(user.getUid()).isInPhoneBook().change(true); - Log.d("ON_CLIENT_PRIVACY", "in record book!"); - break outer; } } } - } - } - - Log.d("ON_CLIENT_PRIVACY", "finish check"); + Log.d("ON_CLIENT_PRIVACY", "finish check"); - resolver.result(null); - })); + resolver.result(null); + }); + } + }); } From 1d4041ff0d54cf855e58d8df201d0ac561d306cf Mon Sep 17 00:00:00 2001 From: rockjam Date: Tue, 11 Oct 2016 21:33:00 +0300 Subject: [PATCH 395/414] fix(server): add optional host name --- .../scala/im/actor/server/cli/ActorCli.scala | 38 ++++++++++++++----- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/actor-server/actor-cli/src/main/scala/im/actor/server/cli/ActorCli.scala b/actor-server/actor-cli/src/main/scala/im/actor/server/cli/ActorCli.scala index ca1a3538d4..65b99fabb9 100644 --- a/actor-server/actor-cli/src/main/scala/im/actor/server/cli/ActorCli.scala +++ b/actor-server/actor-cli/src/main/scala/im/actor/server/cli/ActorCli.scala @@ -15,11 +15,12 @@ import scala.concurrent.duration._ import scala.reflect.ClassTag private case class Config( - command: String = "help", - createBot: CreateBot = CreateBot(), - updateIsAdmin: UpdateIsAdmin = UpdateIsAdmin(), - httpToken: HttpToken = HttpToken(), - key: Key = Key() + command: String = "help", + createBot: CreateBot = CreateBot(), + updateIsAdmin: UpdateIsAdmin = UpdateIsAdmin(), + httpToken: HttpToken = HttpToken(), + key: Key = Key(), + host: Option[String] = None // remote actor system host ) private[cli] trait Request { @@ -75,6 +76,9 @@ object ActorCli extends App { cmd(Commands.CreateBot) action { (_, c) ⇒ c.copy(command = Commands.CreateBot) } children ( + opt[String]("host") abbr "h" optional () action { (x, c) ⇒ + c.copy(host = Some(x)) + }, opt[String]("username") abbr "u" required () action { (x, c) ⇒ c.copy(createBot = c.createBot.copy(username = x)) }, @@ -88,6 +92,9 @@ object ActorCli extends App { cmd(Commands.AdminGrant) action { (_, c) ⇒ c.copy(command = Commands.AdminGrant) } children ( + opt[String]("host") abbr "h" optional () action { (x, c) ⇒ + c.copy(host = Some(x)) + }, opt[Int]("userId") abbr "u" required () action { (x, c) ⇒ c.copy(updateIsAdmin = UpdateIsAdmin(x, isAdmin = true)) } @@ -95,19 +102,29 @@ object ActorCli extends App { cmd(Commands.AdminRevoke) action { (_, c) ⇒ c.copy(command = Commands.AdminRevoke) } children ( + opt[String]("host") abbr "h" optional () action { (x, c) ⇒ + c.copy(host = Some(x)) + }, opt[Int]("userId") abbr "u" required () action { (x, c) ⇒ c.copy(updateIsAdmin = UpdateIsAdmin(x, isAdmin = false)) } ) cmd(Commands.MigrateUserSequence) action { (_, c) ⇒ c.copy(command = Commands.MigrateUserSequence) - } + } children ( + opt[String]("host") abbr "h" optional () action { (x, c) ⇒ + c.copy(host = Some(x)) + } + ) cmd(Commands.HttpToken) action { (_, c) ⇒ c.copy(command = Commands.HttpToken) } children ( cmd("create") action { (_, c) ⇒ c.copy(httpToken = c.httpToken.copy(command = "create")) } children ( + opt[String]("host") abbr "h" optional () action { (x, c) ⇒ + c.copy(host = Some(x)) + }, opt[Unit]("admin") abbr "a" optional () action { (x, c) ⇒ c.copy(httpToken = c.httpToken.copy(create = c.httpToken.create.copy(isAdmin = true))) } @@ -116,6 +133,9 @@ object ActorCli extends App { cmd(Commands.Key) action { (_, c) ⇒ c.copy(command = Commands.Key) } children ( + opt[String]("host") abbr "h" optional () action { (x, c) ⇒ + c.copy(host = Some(x)) + }, opt[Unit]("create") abbr "c" required () action { (x, c) ⇒ c.copy(key = c.key.copy(create = true)) }, @@ -126,7 +146,7 @@ object ActorCli extends App { } parser.parse(args, Config()) foreach { config ⇒ - val handlers = new CliHandlers + val handlers = new CliHandlers(config.host) val migrationHandlers = new MigrationHandlers val securityHandlers = new SecurityHandlers @@ -157,7 +177,7 @@ object ActorCli extends App { } } -final class CliHandlers extends BotHandlers with UsersHandlers with HttpHandlers { +final class CliHandlers(host: Option[String]) extends BotHandlers with UsersHandlers with HttpHandlers { protected val BotService = "bots" protected val UsersService = "users" protected val HttpService = "http" @@ -169,7 +189,7 @@ final class CliHandlers extends BotHandlers with UsersHandlers with HttpHandlers ActorSystem("actor-cli", config) } - protected lazy val remoteHost = InetAddress.getLocalHost.getHostAddress + protected lazy val remoteHost = host.getOrElse(InetAddress.getLocalHost.getHostAddress) protected lazy val initialContacts = Set(ActorPath.fromString(s"akka.tcp://actor-server@$remoteHost:2552/system/receptionist")) From 000add3e777a3771def651324567dbbd2bbd9969 Mon Sep 17 00:00:00 2001 From: rockjam Date: Thu, 13 Oct 2016 02:42:29 +0300 Subject: [PATCH 396/414] feat(server:groups): add group ext methods --- .../actor-core/src/main/protobuf/group.proto | 15 ++++++++++ .../src/main/protobuf/groupV2.proto | 28 ++++++++++++++++++ .../im/actor/server/api/TypeMappers.scala | 14 +++++++++ .../im/actor/server/group/GroupErrors.scala | 2 ++ .../actor/server/group/GroupOperations.scala | 10 +++++++ .../actor/server/group/GroupProcessor.scala | 21 ++++++++++++-- .../server/group/GroupQueryHandlers.scala | 2 +- .../im/actor/server/group/GroupState.scala | 23 ++++++++++----- .../server/group/InfoCommandHandlers.scala | 29 ++++++++++++++++++- 9 files changed, 133 insertions(+), 11 deletions(-) diff --git a/actor-server/actor-core/src/main/protobuf/group.proto b/actor-server/actor-core/src/main/protobuf/group.proto index b0fa464429..183b00c31d 100644 --- a/actor-server/actor-core/src/main/protobuf/group.proto +++ b/actor-server/actor-core/src/main/protobuf/group.proto @@ -168,4 +168,19 @@ message GroupEvents { required int64 ts = 1 [(scalapb.field).type = "java.time.Instant"]; required int32 executor_user_id = 2; } + + message ExtAdded { + option (scalapb.message).extends = "im.actor.server.group.GroupEvent"; + + required int64 ts = 1 [(scalapb.field).type = "java.time.Instant"]; + required bytes ext = 2 [(scalapb.field).type = "im.actor.server.group.GroupExt"]; + } + + message ExtRemoved { + option (scalapb.message).extends = "im.actor.server.group.GroupEvent"; + + required int64 ts = 1 [(scalapb.field).type = "java.time.Instant"]; + required string key = 2; + } + } diff --git a/actor-server/actor-core/src/main/protobuf/groupV2.proto b/actor-server/actor-core/src/main/protobuf/groupV2.proto index 47be829e9c..943b5afd55 100644 --- a/actor-server/actor-core/src/main/protobuf/groupV2.proto +++ b/actor-server/actor-core/src/main/protobuf/groupV2.proto @@ -18,6 +18,15 @@ import "file.proto"; import "sequence.proto"; import "dialog.proto"; +// TODO: Can't share with UserExt: proto 2/3 +message GroupExt { + string key = 1; + oneof value { + string string_value = 2; + bool bool_value = 3; + } +} + message GroupMember { int32 user_id = 1; int32 inviter_user_id = 2; @@ -45,6 +54,8 @@ message GroupEnvelope { GroupCommands.TransferOwnership transfer_ownership = 14; GroupCommands.UpdateAdminSettings update_admin_settings = 30; GroupCommands.DeleteGroup delete_group = 33; + GroupCommands.AddExt add_ext = 34; + GroupCommands.RemoveExt remove_ext = 35; } oneof query { GroupQueries.GetAccessHash get_access_hash = 15; @@ -219,6 +230,23 @@ message GroupCommands { int32 client_user_id = 1; int64 client_auth_id = 2; } + + message AddExt { + option (scalapb.message).extends = "GroupCommand"; + + GroupExt ext = 1; + } + + message AddExtAck {} + + message RemoveExt { + option (scalapb.message).extends = "GroupCommand"; + + string key = 1; + } + + message RemoveExtAck {} + } message GroupQueries { diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/api/TypeMappers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/api/TypeMappers.scala index b7ab276224..49c441821e 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/api/TypeMappers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/api/TypeMappers.scala @@ -17,6 +17,7 @@ import im.actor.api.rpc.sequence.SeqUpdate import im.actor.api.rpc.users.ApiSex.ApiSex import im.actor.api.rpc.users.{ ApiFullUser, ApiUser, ApiSex ⇒ S } import im.actor.serialization.ActorSerializer +import im.actor.server.group.GroupExt import org.joda.time.DateTime abstract class ActorSystemMapper[BaseType, CustomType](implicit val system: ActorSystem) extends TypeMapper[BaseType, CustomType] @@ -184,6 +185,17 @@ private[api] trait MessageMapper { def unapplyExtension(ext: ApiExtension): ByteString = ByteString.copyFrom(ext.toByteArray) + def applyGroupExt(bytes: ByteString): GroupExt = { + if (bytes.size > 0) { + GroupExt.parseFrom(CodedInputStream.newInstance(bytes.asReadOnlyByteBuffer())) + } else { + null + } + } + + def unapplyGroupExt(ext: GroupExt): ByteString = + ByteString.copyFrom(ext.toByteArray) + private def applyAdminSettings(bytes: ByteString): ApiAdminSettings = { if (bytes.size() > 0) { val res = ApiAdminSettings.parseFrom(CodedInputStream.newInstance(bytes.toByteArray)) @@ -228,6 +240,8 @@ private[api] trait MessageMapper { implicit val extensionMapper: TypeMapper[ByteString, ApiExtension] = TypeMapper(applyExtension)(unapplyExtension) + implicit val groupExtMapper: TypeMapper[ByteString, GroupExt] = TypeMapper(applyGroupExt)(unapplyGroupExt) + implicit val adminSettingsMapper: TypeMapper[ByteString, ApiAdminSettings] = TypeMapper(applyAdminSettings)(unapplyAdminSettings) implicit def actorRefMapper(implicit system: ActorSystem): TypeMapper[String, ActorRef] = diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupErrors.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupErrors.scala index 578a11ec66..1decbe7c08 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupErrors.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupErrors.scala @@ -46,4 +46,6 @@ object GroupErrors { case object CantLeaveGroup extends Exception with NoStackTrace final case class IncorrectGroupType(value: Int) extends Exception with NoStackTrace + + case object InvalidExtension extends Exception with NoStackTrace } diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupOperations.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupOperations.scala index 44b1b3f558..8a9724716d 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupOperations.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupOperations.scala @@ -127,6 +127,16 @@ private[group] sealed trait Commands extends UserAcl { GroupEnvelope(groupId) .withDeleteGroup(DeleteGroup(clientUserId, clientAuthId))).mapTo[SeqState] + def addExt(groupId: Int, ext: GroupExt): Future[Unit] = + (processorRegion.ref ? + GroupEnvelope(groupId) + .withAddExt(AddExt(Some(ext)))).mapTo[AddExtAck] map (_ ⇒ ()) + + def removeExt(groupId: Int, key: String): Future[Unit] = + (processorRegion.ref ? + GroupEnvelope(groupId) + .withRemoveExt(RemoveExt(key))).mapTo[RemoveExtAck] map (_ ⇒ ()) + } private[group] sealed trait Queries { diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala index 0496cd7dc7..dd6b8954a7 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupProcessor.scala @@ -7,6 +7,7 @@ import akka.actor.{ ActorRef, ActorSystem, Props, ReceiveTimeout, Status } import akka.cluster.sharding.ShardRegion import akka.http.scaladsl.util.FastFuture import com.github.benmanes.caffeine.cache.{ Cache, Caffeine } +import im.actor.api.rpc.collections.{ ApiInt32Value, ApiMapValue, ApiMapValueItem, ApiStringValue } import im.actor.api.rpc.peers.{ ApiPeer, ApiPeerType } import im.actor.concurrent.ActorFutures import im.actor.serialization.ActorSerializer @@ -15,6 +16,7 @@ import im.actor.server.db.DbExtension import im.actor.server.dialog._ import im.actor.server.group.GroupErrors._ import im.actor.server.group.GroupCommands._ +import im.actor.server.group.GroupExt.Value.{ BoolValue, StringValue } import im.actor.server.group.GroupQueries._ import im.actor.server.names.GlobalNamesStorageKeyValueStorage import im.actor.server.sequence.SeqUpdatesExtension @@ -54,6 +56,8 @@ object GroupProcessor { 20024 → classOf[GroupCommands.UpdateAdminSettings], 20025 → classOf[GroupCommands.MakeHistoryShared], 20026 → classOf[GroupCommands.DeleteGroup], + 20027 → classOf[GroupCommands.AddExt], + 20028 → classOf[GroupCommands.RemoveExt], 21001 → classOf[GroupQueries.GetIntegrationToken], 21002 → classOf[GroupQueries.GetIntegrationTokenResponse], @@ -96,7 +100,9 @@ object GroupProcessor { 22020 → classOf[GroupEvents.AdminStatusChanged], 22021 → classOf[GroupEvents.HistoryBecameShared], 22022 → classOf[GroupEvents.GroupDeleted], - 22023 → classOf[GroupEvents.MembersBecameAsync] + 22023 → classOf[GroupEvents.MembersBecameAsync], + 22024 → classOf[GroupEvents.ExtAdded], + 22025 → classOf[GroupEvents.ExtRemoved] ) def persistenceIdFor(groupId: Int): String = s"Group-${groupId}" @@ -152,6 +158,8 @@ private[group] final class GroupProcessor case u: UpdateTopic ⇒ updateTopic(u) case u: UpdateAbout ⇒ updateAbout(u) case u: UpdateShortName ⇒ updateShortName(u) + case a: AddExt ⇒ addExt(a) + case r: RemoveExt ⇒ removeExt(r) // admin actions case r: RevokeIntegrationToken ⇒ revokeIntegrationToken(r) @@ -194,6 +202,15 @@ private[group] final class GroupProcessor case LoadAdminSettings(clientUserId) ⇒ loadAdminSettings(clientUserId) } + protected def extToApi(exts: Seq[GroupExt]): ApiMapValue = { + ApiMapValue( + exts.toVector map { + case GroupExt(key, BoolValue(b)) ⇒ ApiMapValueItem(key, ApiInt32Value(if (b) 1 else 0)) + case GroupExt(key, StringValue(s)) ⇒ ApiMapValueItem(key, ApiStringValue(s)) + } + ) + } + override def afterCommit(e: Event) = { super.afterCommit(e) if (recoveryFinished) { @@ -202,7 +219,7 @@ private[group] final class GroupProcessor updateCanCall(state) } // from 50+ members we make group with async members - if (state.membersCount >= 50 && !state.isAsyncMembers) { + if (!state.isAsyncMembers && state.membersCount >= 50) { makeMembersAsync() } } diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala index cf68390746..65955c7ff9 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupQueryHandlers.scala @@ -114,7 +114,7 @@ trait GroupQueryHandlers { theme = state.topic, about = state.about, isHidden = Some(state.isHidden), - ext = None, + ext = Some(extToApi(state.exts)), membersCount = Some(count), groupType = Some(state.groupType match { case Channel ⇒ ApiGroupType.CHANNEL diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala index e124305eb1..fa42616a9b 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/GroupState.scala @@ -96,7 +96,8 @@ private[group] object GroupState { deletedAt = None, //??? - extensions = Map.empty + internalExtensions = Map.empty, + exts = Seq.empty ) } @@ -124,11 +125,12 @@ private[group] final case class GroupState( invitedUserIds: Set[Int], //security and etc. - accessHash: Long, - adminSettings: AdminSettings, - bot: Option[Bot], - deletedAt: Option[Instant], - extensions: Map[Int, Array[Byte]] + accessHash: Long, + adminSettings: AdminSettings, + bot: Option[Bot], + deletedAt: Option[Instant], + internalExtensions: Map[Int, Array[Byte]], + exts: Seq[GroupExt] ) extends ProcessorState[GroupState] { lazy val memberIds = members.keySet @@ -200,7 +202,7 @@ private[group] final case class GroupState( if (typeOfGroup.isChannel) AdminSettings.ChannelsDefault else AdminSettings.PlainDefault, bot = None, - extensions = (evt.extensions map { //TODO: validate is it right? + internalExtensions = (evt.extensions map { //TODO: validate is it right? case ApiExtension(extId, data) ⇒ extId → data }).toMap @@ -287,6 +289,13 @@ private[group] final case class GroupState( if (groupType.isChannel) AdminSettings.ChannelsDefault else AdminSettings.PlainDefault ) + case ExtAdded(_, ext) ⇒ + if (exts.contains(ext)) + this + else + this.copy(exts = exts :+ ext) + case ExtRemoved(_, key) ⇒ + this.copy(exts = exts.filterNot(_.key == key)) // deprecated events case UserBecameAdmin(_, userId, _) ⇒ this.copy( diff --git a/actor-server/actor-core/src/main/scala/im/actor/server/group/InfoCommandHandlers.scala b/actor-server/actor-core/src/main/scala/im/actor/server/group/InfoCommandHandlers.scala index 462075a826..bc42b1eebf 100644 --- a/actor-server/actor-core/src/main/scala/im/actor/server/group/InfoCommandHandlers.scala +++ b/actor-server/actor-core/src/main/scala/im/actor/server/group/InfoCommandHandlers.scala @@ -9,7 +9,7 @@ import com.github.ghik.silencer.silent import im.actor.api.rpc.files.ApiAvatar import im.actor.api.rpc.groups._ import im.actor.server.file.{ Avatar, ImageUtils } -import im.actor.server.group.GroupCommands.{ MakeHistoryShared, UpdateAbout, UpdateAvatar, UpdateAvatarAck, UpdateShortName, UpdateTitle, UpdateTopic } +import im.actor.server.group.GroupCommands.{ AddExt, AddExtAck, MakeHistoryShared, RemoveExt, RemoveExtAck, UpdateAbout, UpdateAvatar, UpdateAvatarAck, UpdateShortName, UpdateTitle, UpdateTopic } import im.actor.server.group.GroupErrors._ import im.actor.server.group.GroupEvents.{ AboutUpdated, AvatarUpdated, ShortNameUpdated, TitleUpdated, TopicUpdated } import im.actor.server.model.AvatarData @@ -327,6 +327,33 @@ private[group] trait InfoCommandHandlers { } } + protected def addExt(cmd: AddExt): Unit = + cmd.ext match { + case Some(ext) ⇒ + persist(GroupEvents.ExtAdded(Instant.now, ext)) { evt ⇒ + val newState = commit(evt) + sendExtUpdate(newState) map (_ ⇒ AddExtAck()) pipeTo sender() + } + case None ⇒ + sender() ! Status.Failure(InvalidExtension) + } + + protected def removeExt(cmd: RemoveExt): Unit = + if (state.exts.exists(_.key == cmd.key)) { + persist(GroupEvents.ExtRemoved(Instant.now, cmd.key)) { evt ⇒ + val newState = commit(evt) + sendExtUpdate(newState) map (_ ⇒ RemoveExtAck()) pipeTo sender() + } + } else { + sender() ! RemoveExtAck() + } + + private def sendExtUpdate(state: GroupState): Future[Unit] = + seqUpdExt.broadcastPeopleUpdate( + userIds = state.memberIds, + update = UpdateGroupExtChanged(groupId, Some(extToApi(state.exts))) + ) + private def getAvatarData(avatar: Option[Avatar]): AvatarData = avatar .map(ImageUtils.getAvatarData(AvatarData.OfGroup, groupId, _)) From cfa63b430c46b5c3146504aa5499860623989689 Mon Sep 17 00:00:00 2001 From: rockjam Date: Thu, 13 Oct 2016 03:32:08 +0300 Subject: [PATCH 397/414] feat(server:bots): add/remove ext commands --- .../scala/im/actor/bots/BotMessages.scala | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/actor-server/actor-bots-shared/src/main/scala/im/actor/bots/BotMessages.scala b/actor-server/actor-bots-shared/src/main/scala/im/actor/bots/BotMessages.scala index be715cd660..8a86efdb5c 100644 --- a/actor-server/actor-bots-shared/src/main/scala/im/actor/bots/BotMessages.scala +++ b/actor-server/actor-bots-shared/src/main/scala/im/actor/bots/BotMessages.scala @@ -552,6 +552,41 @@ object BotMessages { final case class ResponseCreateGroup(@beanGetter peer: GroupOutPeer) extends ResponseBody + @key("AddGroupExtString") + final case class AddGroupExtString( + @beanGetter groupId: Int, + @beanGetter key: String, + @beanGetter value: String + ) extends RequestBody { + override type Response = Void + override val service: String = Services.Groups + + override def readResponse(obj: Js.Obj): Response = readJs[Response](obj) + } + + @key("AddGroupExtBool") + final case class AddGroupExtBool( + @beanGetter groupId: Int, + @beanGetter key: String, + @beanGetter value: Boolean + ) extends RequestBody { + override type Response = Void + override val service: String = Services.Groups + + override def readResponse(obj: Js.Obj): Response = readJs[Response](obj) + } + + @key("RemoveGroupExt") + final case class RemoveGroupExt( + @beanGetter groupId: Int, + @beanGetter key: String + ) extends RequestBody { + override type Response = Void + override val service: String = Services.Groups + + override def readResponse(obj: Js.Obj): Response = readJs[Response](obj) + } + @key("InviteUser") final case class InviteUser(@beanGetter groupPeer: GroupOutPeer, @beanGetter userPeer: UserOutPeer) extends RequestBody { override type Response = Void From 8c8a6dfaea14a9f8592f9197578a6f4e0e8a28c1 Mon Sep 17 00:00:00 2001 From: rockjam Date: Thu, 13 Oct 2016 03:46:27 +0300 Subject: [PATCH 398/414] feat(server:bots): add/remove ext methods for groups --- .../bot/services/GroupsBotService.scala | 37 +++++++++++++++++-- actor-server/project/Dependencies.scala | 2 +- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/actor-server/actor-bots/src/main/scala/im/actor/server/bot/services/GroupsBotService.scala b/actor-server/actor-bots/src/main/scala/im/actor/server/bot/services/GroupsBotService.scala index 494f6fb9fe..07c3f66403 100644 --- a/actor-server/actor-bots/src/main/scala/im/actor/server/bot/services/GroupsBotService.scala +++ b/actor-server/actor-bots/src/main/scala/im/actor/server/bot/services/GroupsBotService.scala @@ -2,9 +2,12 @@ package im.actor.server.bot.services import akka.actor.ActorSystem import im.actor.server.bot.{ ApiToBotConversions, BotServiceBase } -import im.actor.server.group.{ GroupExtension, GroupType } +import im.actor.server.group.GroupErrors.InvalidExtension +import im.actor.server.group.GroupExt.Value.{ BoolValue, StringValue } +import im.actor.server.group.{ GroupExt, GroupExtension } import im.actor.util.misc.IdUtils +import scala.concurrent.Future import scala.concurrent.forkjoin.ThreadLocalRandom private[bot] final class GroupsBotService(system: ActorSystem) extends BotServiceBase(system) with ApiToBotConversions { @@ -15,8 +18,11 @@ private[bot] final class GroupsBotService(system: ActorSystem) extends BotServic private val groupExt = GroupExtension(system) override val handlers: Handlers = { - case CreateGroup(title) ⇒ createGroup(title).toWeak - case InviteUser(groupPeer, userPeer) ⇒ inviteUser(groupPeer, userPeer).toWeak + case CreateGroup(title) ⇒ createGroup(title).toWeak + case AddGroupExtString(groupId, key, value) ⇒ addGroupExtString(groupId, key, value).toWeak + case AddGroupExtBool(groupId, key, value) ⇒ addGroupExtBool(groupId, key, value).toWeak + case RemoveGroupExt(groupId, key) ⇒ removeExt(groupId, key).toWeak + case InviteUser(groupPeer, userPeer) ⇒ inviteUser(groupPeer, userPeer).toWeak } private def createGroup(title: String) = RequestHandler[CreateGroup, CreateGroup#Response]( @@ -48,4 +54,29 @@ private[bot] final class GroupsBotService(system: ActorSystem) extends BotServic } yield Right(Void) } ) + + private def addGroupExtString(groupId: Int, key: String, value: String) = RequestHandler[AddGroupExtString, AddGroupExtString#Response]( + (botUserId: Int, botAuthId: Long, botAuthSid: Int) ⇒ { + (for (_ ← addExt(groupId, GroupExt(key, StringValue(value)))) yield Right(Void)) recover { + case InvalidExtension ⇒ Left(BotError(500, "INVALID_EXT")) + } + } + ) + + private def addGroupExtBool(groupId: Int, key: String, value: Boolean) = RequestHandler[AddGroupExtBool, AddGroupExtBool#Response]( + (botUserId: Int, botAuthId: Long, botAuthSid: Int) ⇒ { + (for (_ ← addExt(groupId, GroupExt(key, BoolValue(value)))) yield Right(Void)) recover { + case InvalidExtension ⇒ Left(BotError(500, "INVALID_EXT")) + } + } + ) + + private def removeExt(groupId: Int, key: String) = RequestHandler[RemoveGroupExt, RemoveGroupExt#Response]( + (botUserId: Int, botAuthId: Long, botAuthSid: Int) ⇒ { + groupExt.removeExt(groupId, key) map (_ ⇒ Right(Void)) + } + ) + + private def addExt(groupId: Int, ext: GroupExt): Future[Unit] = groupExt.addExt(groupId: Int, ext: GroupExt) + } diff --git a/actor-server/project/Dependencies.scala b/actor-server/project/Dependencies.scala index 37c6c11eb3..a72b7b87b9 100644 --- a/actor-server/project/Dependencies.scala +++ b/actor-server/project/Dependencies.scala @@ -5,7 +5,7 @@ import sbt._ object Dependencies { object V { val actorCommons = "0.0.20" - val actorBotkit = "1.0.109" + val actorBotkit = "1.0.110" val akka = "2.4.10" val akkaHttpJson = "1.10.0" val cats = "0.7.2" From 9377069a71bd1747212a31296f11d8fe1da9dcdd Mon Sep 17 00:00:00 2001 From: rockjam Date: Thu, 13 Oct 2016 04:09:47 +0300 Subject: [PATCH 399/414] fix(server:bots): allow only admin bot to edit group ext --- .../server/bot/services/GroupsBotService.scala | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/actor-server/actor-bots/src/main/scala/im/actor/server/bot/services/GroupsBotService.scala b/actor-server/actor-bots/src/main/scala/im/actor/server/bot/services/GroupsBotService.scala index 07c3f66403..2b9f20e9f3 100644 --- a/actor-server/actor-bots/src/main/scala/im/actor/server/bot/services/GroupsBotService.scala +++ b/actor-server/actor-bots/src/main/scala/im/actor/server/bot/services/GroupsBotService.scala @@ -57,23 +57,29 @@ private[bot] final class GroupsBotService(system: ActorSystem) extends BotServic private def addGroupExtString(groupId: Int, key: String, value: String) = RequestHandler[AddGroupExtString, AddGroupExtString#Response]( (botUserId: Int, botAuthId: Long, botAuthSid: Int) ⇒ { - (for (_ ← addExt(groupId, GroupExt(key, StringValue(value)))) yield Right(Void)) recover { - case InvalidExtension ⇒ Left(BotError(500, "INVALID_EXT")) + ifIsAdmin(botUserId) { + (for (_ ← addExt(groupId, GroupExt(key, StringValue(value)))) yield Right(Void)) recover { + case InvalidExtension ⇒ Left(BotError(500, "INVALID_EXT")) + } } } ) private def addGroupExtBool(groupId: Int, key: String, value: Boolean) = RequestHandler[AddGroupExtBool, AddGroupExtBool#Response]( (botUserId: Int, botAuthId: Long, botAuthSid: Int) ⇒ { - (for (_ ← addExt(groupId, GroupExt(key, BoolValue(value)))) yield Right(Void)) recover { - case InvalidExtension ⇒ Left(BotError(500, "INVALID_EXT")) + ifIsAdmin(botUserId) { + (for (_ ← addExt(groupId, GroupExt(key, BoolValue(value)))) yield Right(Void)) recover { + case InvalidExtension ⇒ Left(BotError(500, "INVALID_EXT")) + } } } ) private def removeExt(groupId: Int, key: String) = RequestHandler[RemoveGroupExt, RemoveGroupExt#Response]( (botUserId: Int, botAuthId: Long, botAuthSid: Int) ⇒ { - groupExt.removeExt(groupId, key) map (_ ⇒ Right(Void)) + ifIsAdmin(botUserId) { + groupExt.removeExt(groupId, key) map (_ ⇒ Right(Void)) + } } ) From 4caad9c3cf4b2c442679ac9ef3e490d15c1fb0ad Mon Sep 17 00:00:00 2001 From: rockjam Date: Thu, 13 Oct 2016 18:23:02 +0300 Subject: [PATCH 400/414] refactor(server:activation): better error logging, add custom.conf --- .../server/activation/smtp/SMTPProvider.scala | 11 +++++--- .../src/main/resources/reference.conf | 4 +-- .../actor/server/email/EmailExtension.scala | 2 +- .../src/docker/var/lib/actor/conf/custom.conf | 1 + .../src/docker/var/lib/actor/conf/server.conf | 26 ++++++++++++++++++- 5 files changed, 36 insertions(+), 8 deletions(-) create mode 100644 actor-server/src/docker/var/lib/actor/conf/custom.conf diff --git a/actor-server/actor-activation/src/main/scala/im/actor/server/activation/smtp/SMTPProvider.scala b/actor-server/actor-activation/src/main/scala/im/actor/server/activation/smtp/SMTPProvider.scala index 459d9d92f7..bdea067324 100644 --- a/actor-server/actor-activation/src/main/scala/im/actor/server/activation/smtp/SMTPProvider.scala +++ b/actor-server/actor-activation/src/main/scala/im/actor/server/activation/smtp/SMTPProvider.scala @@ -12,13 +12,14 @@ import im.actor.env.ActorEnv import im.actor.server.activation.common.ActivationStateActor.{ ForgetSentCode, Send, SendAck } import im.actor.server.activation.common._ import im.actor.server.db.DbExtension -import im.actor.server.email.{ Content, EmailConfig, Message, SmtpEmailSender } +import im.actor.server.email._ import im.actor.server.model.AuthEmailTransaction import im.actor.server.persist.auth.AuthTransactionRepo import im.actor.util.misc.EmailUtils.isTestEmail import scala.concurrent.Future import scala.concurrent.duration._ +import scala.util.Try private[activation] final class SMTPProvider(system: ActorSystem) extends ActivationProvider with CommonAuthCodes { @@ -28,12 +29,14 @@ private[activation] final class SMTPProvider(system: ActorSystem) extends Activa private implicit val timeout = Timeout(20.seconds) - private val emailConfig = EmailConfig.load.getOrElse(throw new RuntimeException("Failed to load email config")) - private val emailSender = new SmtpEmailSender(emailConfig) + private val emailSender = EmailExtension(system).sender + private val emailTemplateLocation = ActorEnv.getAbsolutePath(Paths.get(ActorConfig.load().getString("services.activation.email.template"))) - private val emailTemplate = new String(Files.readAllBytes(emailTemplateLocation)) + private val emailTemplate = + Try(new String(Files.readAllBytes(emailTemplateLocation))) + .getOrElse(throw new RuntimeException(s"Failed to read template file. Make sure you put it in ${emailTemplateLocation}")) private val smtpStateActor = system.actorOf(ActivationStateActor.props[String, EmailCode]( repeatLimit = activationConfig.repeatLimit, diff --git a/actor-server/actor-email/src/main/resources/reference.conf b/actor-server/actor-email/src/main/resources/reference.conf index 1edcf9a184..7972935b3e 100644 --- a/actor-server/actor-email/src/main/resources/reference.conf +++ b/actor-server/actor-email/src/main/resources/reference.conf @@ -10,9 +10,9 @@ services { host: "smtp.gmail.com" port: 465 username: "no-reply@actor.im" - username: ${?SMTP_USERNAME} + username: ${?ACTOR_SMTP_USERNAME} password: "pass" - password = ${?SMTP_PASSWORD} + password: ${?ACTOR_SMTP_PASSWORD} tls: true } } diff --git a/actor-server/actor-email/src/main/scala/im/actor/server/email/EmailExtension.scala b/actor-server/actor-email/src/main/scala/im/actor/server/email/EmailExtension.scala index eb95967a64..b58b77e2dd 100644 --- a/actor-server/actor-email/src/main/scala/im/actor/server/email/EmailExtension.scala +++ b/actor-server/actor-email/src/main/scala/im/actor/server/email/EmailExtension.scala @@ -6,7 +6,7 @@ sealed trait EmailExtension extends Extension final class EmailExtensionImpl(system: ActorSystem) extends EmailExtension { import system.dispatcher - private val config = EmailConfig.load.get + private val config = EmailConfig.load.getOrElse(throw new RuntimeException("Failed to load email config")) val sender: EmailSender = new SmtpEmailSender(config) } diff --git a/actor-server/src/docker/var/lib/actor/conf/custom.conf b/actor-server/src/docker/var/lib/actor/conf/custom.conf new file mode 100644 index 0000000000..cee6a29e8e --- /dev/null +++ b/actor-server/src/docker/var/lib/actor/conf/custom.conf @@ -0,0 +1 @@ +# file to include custom configuration, alongside with server.conf diff --git a/actor-server/src/docker/var/lib/actor/conf/server.conf b/actor-server/src/docker/var/lib/actor/conf/server.conf index 7cf2dd61ab..6f62ce904d 100644 --- a/actor-server/src/docker/var/lib/actor/conf/server.conf +++ b/actor-server/src/docker/var/lib/actor/conf/server.conf @@ -1,4 +1,4 @@ -// We need it in application.conf because reference.conf can't refer to application.conf, this is a work-around +include file("conf/custom.conf") secret: ${?ACTOR_SECRET} @@ -33,6 +33,30 @@ services { location: "/files" location: ${?ACTOR_FILESTORAGE_LOCATION} } + + email { + sender { + address: "" + address: ${?ACTOR_EMAIL_SENDER_ADDRESS} + name: "Actor" + name: ${?ACTOR_EMAIL_SENDER_NAME} + prefix: "[Actor]" + } + + smtp { + host: "" + host: ${?ACTOR_SMTP_HOST} + port: 465 + port: ${?ACTOR_SMTP_PORT} + username: "" + username: ${?ACTOR_SMTP_USERNAME} + password: "" + password: ${?ACTOR_SMTP_PASSWORD} + tls: true + password: ${?ACTOR_SMTP_TLS} + } + } + } http { From 14ab8a81f7089f297c0c4fa945f5e33e9d9ac686 Mon Sep 17 00:00:00 2001 From: rockjam Date: Thu, 13 Oct 2016 21:41:36 +0300 Subject: [PATCH 401/414] feat(bots): group creation with specified owner; update short name for group --- .../scala/im/actor/bots/BotMessages.scala | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/actor-server/actor-bots-shared/src/main/scala/im/actor/bots/BotMessages.scala b/actor-server/actor-bots-shared/src/main/scala/im/actor/bots/BotMessages.scala index 8a86efdb5c..d828afb438 100644 --- a/actor-server/actor-bots-shared/src/main/scala/im/actor/bots/BotMessages.scala +++ b/actor-server/actor-bots-shared/src/main/scala/im/actor/bots/BotMessages.scala @@ -542,8 +542,14 @@ object BotMessages { @key("CreateGroup") final case class CreateGroup( - title: String + @beanGetter title: String, + ownerUserId: Option[Int] ) extends RequestBody { + def this(title: String) = this(title, None) + def this(title: String, ownerUserId: Int) = this(title, Option(ownerUserId)) + + def getOwnerUserId = ownerUserId.asJava + override type Response = ResponseCreateGroup override val service: String = Services.Groups @@ -552,6 +558,22 @@ object BotMessages { final case class ResponseCreateGroup(@beanGetter peer: GroupOutPeer) extends ResponseBody + @key("UpdateGroupShortName") + final case class UpdateGroupShortName( + @beanGetter groupId: Int, + shortName: Option[String] + ) extends RequestBody { + def this(groupId: Int, shortName: String) = this(groupId, Option(shortName)) + + def getShortName = shortName.asJava + + override type Response = Void + override val service: String = Services.Groups + + override def readResponse(obj: Js.Obj): Response = readJs[Response](obj) + } + + @key("AddGroupExtString") final case class AddGroupExtString( @beanGetter groupId: Int, From 80bf1a0cb9c8d17fe4c1288675dd64b2bc99cdd7 Mon Sep 17 00:00:00 2001 From: rockjam Date: Thu, 13 Oct 2016 23:06:20 +0300 Subject: [PATCH 402/414] fix(server:botkit): methods to create group --- .../scala/im/actor/bots/BotMessages.scala | 36 ++++++++++--------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/actor-server/actor-bots-shared/src/main/scala/im/actor/bots/BotMessages.scala b/actor-server/actor-bots-shared/src/main/scala/im/actor/bots/BotMessages.scala index d828afb438..41b25b906c 100644 --- a/actor-server/actor-bots-shared/src/main/scala/im/actor/bots/BotMessages.scala +++ b/actor-server/actor-bots-shared/src/main/scala/im/actor/bots/BotMessages.scala @@ -542,14 +542,19 @@ object BotMessages { @key("CreateGroup") final case class CreateGroup( - @beanGetter title: String, - ownerUserId: Option[Int] + @beanGetter title: String ) extends RequestBody { - def this(title: String) = this(title, None) - def this(title: String, ownerUserId: Int) = this(title, Option(ownerUserId)) + override type Response = ResponseCreateGroup + override val service: String = Services.Groups - def getOwnerUserId = ownerUserId.asJava + override def readResponse(obj: Js.Obj): Response = readJs[Response](obj) + } + @key("CreateGroupWithOwner") + final case class CreateGroupWithOwner( + @beanGetter title: String, + @beanGetter user: UserPeer + ) extends RequestBody { override type Response = ResponseCreateGroup override val service: String = Services.Groups @@ -560,9 +565,9 @@ object BotMessages { @key("UpdateGroupShortName") final case class UpdateGroupShortName( - @beanGetter groupId: Int, - shortName: Option[String] - ) extends RequestBody { + @beanGetter groupId: Int, + shortName: Option[String] + ) extends RequestBody { def this(groupId: Int, shortName: String) = this(groupId, Option(shortName)) def getShortName = shortName.asJava @@ -573,12 +578,11 @@ object BotMessages { override def readResponse(obj: Js.Obj): Response = readJs[Response](obj) } - @key("AddGroupExtString") final case class AddGroupExtString( @beanGetter groupId: Int, - @beanGetter key: String, - @beanGetter value: String + @beanGetter key: String, + @beanGetter value: String ) extends RequestBody { override type Response = Void override val service: String = Services.Groups @@ -589,8 +593,8 @@ object BotMessages { @key("AddGroupExtBool") final case class AddGroupExtBool( @beanGetter groupId: Int, - @beanGetter key: String, - @beanGetter value: Boolean + @beanGetter key: String, + @beanGetter value: Boolean ) extends RequestBody { override type Response = Void override val service: String = Services.Groups @@ -601,7 +605,7 @@ object BotMessages { @key("RemoveGroupExt") final case class RemoveGroupExt( @beanGetter groupId: Int, - @beanGetter key: String + @beanGetter key: String ) extends RequestBody { override type Response = Void override val service: String = Services.Groups @@ -906,8 +910,8 @@ object BotMessages { @key("AnimationVid") final case class DocumentExAnimationVid( - @beanGetter width: Int, - @beanGetter height: Int, + @beanGetter width: Int, + @beanGetter height: Int, @beanGetter duration: Int ) extends DocumentEx From fa34dab64fdfbd87b6a8db260c252f426b169588 Mon Sep 17 00:00:00 2001 From: rockjam Date: Thu, 13 Oct 2016 23:21:09 +0300 Subject: [PATCH 403/414] feat(server:bots): add short name via to group via bot --- .../bot/services/GroupsBotService.scala | 79 ++++++++++++++----- actor-server/project/Dependencies.scala | 2 +- 2 files changed, 60 insertions(+), 21 deletions(-) diff --git a/actor-server/actor-bots/src/main/scala/im/actor/server/bot/services/GroupsBotService.scala b/actor-server/actor-bots/src/main/scala/im/actor/server/bot/services/GroupsBotService.scala index 2b9f20e9f3..857e9e7a54 100644 --- a/actor-server/actor-bots/src/main/scala/im/actor/server/bot/services/GroupsBotService.scala +++ b/actor-server/actor-bots/src/main/scala/im/actor/server/bot/services/GroupsBotService.scala @@ -1,8 +1,9 @@ package im.actor.server.bot.services import akka.actor.ActorSystem +import im.actor.bots.BotMessages.BotError import im.actor.server.bot.{ ApiToBotConversions, BotServiceBase } -import im.actor.server.group.GroupErrors.InvalidExtension +import im.actor.server.group.GroupErrors.{ InvalidExtension, InvalidShortName, NoPermission, ShortNameTaken } import im.actor.server.group.GroupExt.Value.{ BoolValue, StringValue } import im.actor.server.group.{ GroupExt, GroupExtension } import im.actor.util.misc.IdUtils @@ -10,6 +11,10 @@ import im.actor.util.misc.IdUtils import scala.concurrent.Future import scala.concurrent.forkjoin.ThreadLocalRandom +private[bot] object GroupsBotErrors { + val Forbidden = BotError(403, "FORBIDDEN") +} + private[bot] final class GroupsBotService(system: ActorSystem) extends BotServiceBase(system) with ApiToBotConversions { import im.actor.bots.BotMessages._ @@ -18,28 +23,62 @@ private[bot] final class GroupsBotService(system: ActorSystem) extends BotServic private val groupExt = GroupExtension(system) override val handlers: Handlers = { - case CreateGroup(title) ⇒ createGroup(title).toWeak - case AddGroupExtString(groupId, key, value) ⇒ addGroupExtString(groupId, key, value).toWeak - case AddGroupExtBool(groupId, key, value) ⇒ addGroupExtBool(groupId, key, value).toWeak - case RemoveGroupExt(groupId, key) ⇒ removeExt(groupId, key).toWeak - case InviteUser(groupPeer, userPeer) ⇒ inviteUser(groupPeer, userPeer).toWeak + case CreateGroup(title) ⇒ createGroup(title).toWeak + case CreateGroupWithOwner(title, ownerUserId) ⇒ createGroupWithOwner(title, ownerUserId).toWeak + case UpdateGroupShortName(groupId, shortName) ⇒ updateShortName(groupId, shortName).toWeak + case AddGroupExtString(groupId, key, value) ⇒ addGroupExtString(groupId, key, value).toWeak + case AddGroupExtBool(groupId, key, value) ⇒ addGroupExtBool(groupId, key, value).toWeak + case RemoveGroupExt(groupId, key) ⇒ removeExt(groupId, key).toWeak + case InviteUser(groupPeer, userPeer) ⇒ inviteUser(groupPeer, userPeer).toWeak } private def createGroup(title: String) = RequestHandler[CreateGroup, CreateGroup#Response]( - (botUserId: Int, botAuthId: Long, botAuthSid: Int) ⇒ { - val groupId = IdUtils.nextIntId() - val randomId = ThreadLocalRandom.current().nextLong() + (botUserId: Int, _: Long, _: Int) ⇒ { + create(title, botUserId) + } + ) - for { - ack ← groupExt.create( - groupId = groupId, - clientUserId = botUserId, - clientAuthId = 0L, - title = title, - randomId = randomId, - userIds = Set.empty - ) - } yield Right(ResponseCreateGroup(GroupOutPeer(groupId, ack.accessHash))) + private def createGroupWithOwner(title: String, userPeer: UserPeer) = RequestHandler[CreateGroup, CreateGroup#Response]( + (botUserId: Int, _: Long, _: Int) ⇒ { + ifIsAdmin(botUserId) { + create(title, userPeer.id) + } + } + ) + + private def create(title: String, ownerUserId: Int) = { + val groupId = IdUtils.nextIntId() + val randomId = ThreadLocalRandom.current().nextLong() + + for { + ack ← groupExt.create( + groupId = groupId, + clientUserId = ownerUserId, + clientAuthId = 0L, + title = title, + randomId = randomId, + userIds = Set.empty + ) + } yield Right(ResponseCreateGroup(GroupOutPeer(groupId, ack.accessHash))) + } + + private def updateShortName(groupId: Int, shortName: Option[String]) = RequestHandler[UpdateGroupShortName, UpdateGroupShortName#Response]( + (botUserId: Int, _: Long, _: Int) ⇒ { + ifIsAdmin(botUserId) { + (for { + group ← groupExt.getApiFullStruct(groupId, 0) + _ ← groupExt.updateShortName( + groupId = groupId, + clientUserId = group.ownerUserId.getOrElse(0), + clientAuthId = 0L, + shortName = shortName + ) + } yield Right(Void)) recover { + case NoPermission ⇒ Left(GroupsBotErrors.Forbidden) + case InvalidShortName ⇒ Left(BotError(400, "INVALID_SHORT_NAME")) + case ShortNameTaken ⇒ Left(BotError(400, "SHORT_NAME_ALREADY_TAKEN")) + } + } } ) @@ -56,7 +95,7 @@ private[bot] final class GroupsBotService(system: ActorSystem) extends BotServic ) private def addGroupExtString(groupId: Int, key: String, value: String) = RequestHandler[AddGroupExtString, AddGroupExtString#Response]( - (botUserId: Int, botAuthId: Long, botAuthSid: Int) ⇒ { + (botUserId: Int, _: Long, _: Int) ⇒ { ifIsAdmin(botUserId) { (for (_ ← addExt(groupId, GroupExt(key, StringValue(value)))) yield Right(Void)) recover { case InvalidExtension ⇒ Left(BotError(500, "INVALID_EXT")) diff --git a/actor-server/project/Dependencies.scala b/actor-server/project/Dependencies.scala index a72b7b87b9..b98462b02a 100644 --- a/actor-server/project/Dependencies.scala +++ b/actor-server/project/Dependencies.scala @@ -5,7 +5,7 @@ import sbt._ object Dependencies { object V { val actorCommons = "0.0.20" - val actorBotkit = "1.0.110" + val actorBotkit = "1.0.112" val akka = "2.4.10" val akkaHttpJson = "1.10.0" val cats = "0.7.2" From 85e220ea507c192a7bfc6ca8b92a12ad05f624a0 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Mon, 17 Oct 2016 20:09:16 +0300 Subject: [PATCH 404/414] feat(android): input bar fragment new messages callback --- .../conversation/ChatFragment.java | 4 ++- .../inputbar/InputBarFragment.java | 35 +++++++++++++++++-- .../messages/MessagesFragment.java | 23 +++++++++++- .../sdk/view/emoji/keyboard/BaseKeyboard.java | 8 +++-- 4 files changed, 64 insertions(+), 6 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatFragment.java index c9afd92537..dd8ac7f636 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatFragment.java @@ -113,9 +113,11 @@ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, if (quoteFragment == null) { quoteFragment = new QuoteFragment(); } + MessagesDefaultFragment messagesDefaultFragment = MessagesDefaultFragment.create(peer); + messagesDefaultFragment.setNewMessageListener(inputBarFragment); getChildFragmentManager().beginTransaction() .add(toolbarFragment, "toolbar") - .add(R.id.messagesFragment, MessagesDefaultFragment.create(peer)) + .add(R.id.messagesFragment, messagesDefaultFragment) .add(R.id.sendFragment, inputBarFragment) .add(R.id.quoteFragment, quoteFragment) .add(R.id.emptyPlaceholder, new EmptyChatPlaceholder()) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/inputbar/InputBarFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/inputbar/InputBarFragment.java index 47a3ed8a3b..b4c8df0f88 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/inputbar/InputBarFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/inputbar/InputBarFragment.java @@ -7,6 +7,7 @@ import android.content.res.Configuration; import android.os.Build; import android.os.Bundle; +import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.support.v4.content.ContextCompat; @@ -25,6 +26,7 @@ import android.widget.ImageView; import android.widget.TextView; +import im.actor.core.entity.Message; import im.actor.runtime.Log; import im.actor.runtime.actors.ActorRef; import im.actor.runtime.actors.ActorSystem; @@ -33,6 +35,8 @@ import im.actor.sdk.ActorSDK; import im.actor.sdk.R; import im.actor.sdk.controllers.BaseFragment; +import im.actor.sdk.controllers.conversation.messages.MessagesDefaultFragment; +import im.actor.sdk.controllers.conversation.messages.MessagesFragment; import im.actor.sdk.core.audio.VoiceCaptureActor; import im.actor.sdk.util.KeyboardHelper; import im.actor.sdk.util.Screen; @@ -47,7 +51,7 @@ import static im.actor.sdk.util.ViewUtils.zoomOutView; import static im.actor.sdk.view.emoji.SmileProcessor.emoji; -public class InputBarFragment extends BaseFragment { +public class InputBarFragment extends BaseFragment implements MessagesDefaultFragment.NewMessageListener { private static final int SLIDE_LIMIT = Screen.dp(180); private static final int PERMISSION_REQUEST_RECORD_AUDIO = 1; @@ -89,6 +93,7 @@ public class InputBarFragment extends BaseFragment { // Emoji keyboard protected EmojiKeyboard emojiKeyboard; protected ImageView emojiButton; + private Message lastMessage; @Override public void onCreate(Bundle saveInstance) { @@ -194,7 +199,7 @@ public void afterTextChanged(Editable editable) { // emojiButton = (ImageView) res.findViewById(R.id.ib_emoji); emojiButton.setOnClickListener(v -> emojiKeyboard.toggle()); - emojiKeyboard = new EmojiKeyboard(getActivity(), messageEditText); + emojiKeyboard = getEmojiKeyboard(); emojiKeyboard.setOnStickerClickListener(sticker -> { Fragment parent = getParentFragment(); if (parent instanceof InputBarCallback) { @@ -255,6 +260,21 @@ public void onShow() { return res; } + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + if (lastMessage != null) { + onNewMessage(lastMessage); + lastMessage = null; + } + + } + + @NonNull + protected EmojiKeyboard getEmojiKeyboard() { + return new EmojiKeyboard(getActivity(), messageEditText); + } + public void requestFocus() { messageEditText.requestFocus(); keyboardUtils.setImeVisibility(messageEditText, true); @@ -614,4 +634,15 @@ public void onConfigurationChanged(Configuration newConfig) { public boolean onBackPressed() { return emojiKeyboard.onBackPressed(); } + + @Override + public void onNewMessage(Message m) { + if (emojiKeyboard == null) { + // Inputbar fragment not yet created, store last message for later use + lastMessage = m; + } + if (emojiKeyboard instanceof MessagesFragment.NewMessageListener) { + ((MessagesFragment.NewMessageListener) emojiKeyboard).onNewMessage(m); + } + } } \ No newline at end of file diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/MessagesFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/MessagesFragment.java index df180a6f63..f1ab43739a 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/MessagesFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/MessagesFragment.java @@ -45,6 +45,7 @@ public abstract class MessagesFragment extends DisplayListFragment onCreateDisplayList() { if (displayList.getListProcessor() == null) { displayList.setListProcessor(new ChatListProcessor(peer, this.getContext())); } + notifyNewMessage(displayList); return displayList; } @@ -200,7 +202,7 @@ private void recalculateUnreadMessageIfNeeded() { } // refresh list if top message is too old - if (getDisplayList().getItem(0).getSortDate() < firstUnread && !reloaded) { + if (getLastMessage(getDisplayList()).getSortDate() < firstUnread && !reloaded) { reloaded = true; getDisplayList().initCenter(firstUnread, true); return; @@ -296,6 +298,17 @@ public void onResume() { public void onCollectionChanged() { super.onCollectionChanged(); recalculateUnreadMessageIfNeeded(); + notifyNewMessage(getDisplayList()); + } + + protected void notifyNewMessage(BindedDisplayList displayList) { + if (newMessageListener != null && displayList.getSize() > 0) { + newMessageListener.onNewMessage(getLastMessage(displayList)); + } + } + + public Message getLastMessage(BindedDisplayList displayList) { + return displayList.getItem(0); } @Override @@ -316,4 +329,12 @@ public void onDestroyView() { messagesAdapter = null; } } + + public void setNewMessageListener(NewMessageListener newMessageListener) { + this.newMessageListener = newMessageListener; + } + + public interface NewMessageListener { + void onNewMessage(Message m); + } } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/emoji/keyboard/BaseKeyboard.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/emoji/keyboard/BaseKeyboard.java index 5fb86599b4..1466964b30 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/emoji/keyboard/BaseKeyboard.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/view/emoji/keyboard/BaseKeyboard.java @@ -44,7 +44,7 @@ public class BaseKeyboard implements Boolean pendingOpen = false; - private KeyboardStatusListener keyboardStatusListener; + protected KeyboardStatusListener keyboardStatusListener; final WindowManager windowManager; int keyboardHeight; @@ -93,6 +93,10 @@ public void setKeyboardStatusListener(KeyboardStatusListener keyboardStatusListe public void show() { + if (isShowing()) { + return; + } + softKeyboardListeningEnabled = true; this.root = (KeyboardLayout) messageBody.getRootView().findViewById(R.id.container).getParent(); this.container = (RelativeLayout) messageBody.getRootView().findViewById(R.id.container); @@ -111,7 +115,7 @@ public void show() { } - public void showInternal() { + protected void showInternal() { if (isShowing()) { return; } From 83d813aed7169cbeeb7637f034a6856d3dd6dace Mon Sep 17 00:00:00 2001 From: kor_ka Date: Mon, 24 Oct 2016 14:53:48 +0300 Subject: [PATCH 405/414] feat(core): add ext to group VM --- .../src/main/java/im/actor/core/entity/Group.java | 7 +++++++ .../src/main/java/im/actor/core/viewmodel/GroupVM.java | 6 ++++++ 2 files changed, 13 insertions(+) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Group.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Group.java index eb619df85a..6b16f783f9 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Group.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Group.java @@ -68,6 +68,8 @@ public class Group extends WrapperExtEntity implements K private boolean isCanJoin; @Property("readonly, nonatomic") private boolean isCanViewInfo; + @Property("readonly, nonatomic") + private ApiMapValue ext; @NotNull @Property("readonly, nonatomic") @SuppressWarnings("NullableProblems") @@ -296,6 +298,10 @@ public boolean isCanDeleteForeign() { return isCanDeleteForeign; } + public ApiMapValue getExt() { + return ext; + } + public Group updateExt(@Nullable ApiGroupFull ext) { return new Group(getWrapped(), ext); } @@ -746,6 +752,7 @@ protected void applyWrapped(@NotNull ApiGroup wrapped, @Nullable ApiGroupFull ex this.membersCount = wrapped.getMembersCount() != null ? wrapped.getMembersCount() : 0; this.isMember = wrapped.isMember() != null ? wrapped.isMember() : true; this.isDeleted = wrapped.isDeleted() != null ? wrapped.isDeleted() : false; + this.ext = wrapped.getExt(); if (wrapped.getGroupType() == null) { this.groupType = GroupType.GROUP; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java index 7d2c77515e..f38d55d5ec 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java @@ -13,6 +13,7 @@ import java.util.Collection; import java.util.HashSet; +import im.actor.core.api.ApiMapValue; import im.actor.core.entity.Group; import im.actor.core.entity.GroupMember; import im.actor.core.entity.GroupType; @@ -132,6 +133,9 @@ public class GroupVM extends BaseValueModel { @NotNull @Property("nonatomic, readonly") private ValueModel presence; + @NotNull + @Property("nonatomic, readonly") + private ValueModel ext; @NotNull private ArrayList> listeners = new ArrayList<>(); @@ -178,6 +182,7 @@ public GroupVM(@NotNull Group rawObj) { this.theme = new StringValueModel("group." + groupId + ".theme", rawObj.getTopic()); this.about = new StringValueModel("group." + groupId + ".about", rawObj.getAbout()); this.shortName = new StringValueModel("group." + groupId + ".shortname", rawObj.getShortName()); + this.ext = new ValueModel<>("group." + groupId + ".ext", null); } /** @@ -611,6 +616,7 @@ protected void updateValues(@NotNull Group rawObj) { isChanged |= isCanViewInfo.change(rawObj.isCanViewInfo()); isChanged |= isCanJoin.change(rawObj.isCanJoin()); isChanged |= isCanCall.change(rawObj.isCanCall()); + isChanged |= ext.change(rawObj.getExt()); if (isChanged) { notifyIfNeeded(); From a89840f347153a50155ac971019e33ceaf4505ac Mon Sep 17 00:00:00 2001 From: kor_ka Date: Mon, 24 Oct 2016 15:07:59 +0300 Subject: [PATCH 406/414] chore(core): add group ext getter --- .../src/main/java/im/actor/core/viewmodel/GroupVM.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java index f38d55d5ec..9372d3512e 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java @@ -534,6 +534,15 @@ public ValueModel getPresence() { return presence; } + /** + * Get ext Value Model + * + * @return Value Model of ext + */ + @NotNull + public ValueModel getExt() { + return ext; + } /** * Subscribe for GroupVM updates From fb9d1b45ce68c62fdedd0e0294ae101135102f29 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Mon, 24 Oct 2016 16:44:11 +0300 Subject: [PATCH 407/414] fix(core): groupVM add initial ext value --- .../src/main/java/im/actor/core/viewmodel/GroupVM.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java index 9372d3512e..dbffde90c5 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/GroupVM.java @@ -182,7 +182,7 @@ public GroupVM(@NotNull Group rawObj) { this.theme = new StringValueModel("group." + groupId + ".theme", rawObj.getTopic()); this.about = new StringValueModel("group." + groupId + ".about", rawObj.getAbout()); this.shortName = new StringValueModel("group." + groupId + ".shortname", rawObj.getShortName()); - this.ext = new ValueModel<>("group." + groupId + ".ext", null); + this.ext = new ValueModel<>("group." + groupId + ".ext", rawObj.getExt()); } /** From d91558d23a1e6a023f4fce16f30434bfca7b4ce5 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Tue, 25 Oct 2016 17:57:54 +0300 Subject: [PATCH 408/414] feat(core): add json content description --- .../im/actor/core/entity/ContentDescription.java | 2 ++ .../im/actor/core/entity/content/JsonContent.java | 13 +++++++++++++ 2 files changed, 15 insertions(+) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/ContentDescription.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/ContentDescription.java index 9452bee01f..0723cf7ecf 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/ContentDescription.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/ContentDescription.java @@ -99,6 +99,8 @@ public static ContentDescription fromContent(AbsContent msg) { } else if (msg instanceof ServiceContent) { return new ContentDescription(ContentType.SERVICE, ((ServiceContent) msg).getCompatText(), 0, false); + } else if (msg instanceof JsonContent) { + return new ContentDescription(ContentType.CUSTOM_JSON_MESSAGE, ((JsonContent) msg).getContentDescription()); } else { return new ContentDescription(ContentType.UNKNOWN_CONTENT); } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/content/JsonContent.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/content/JsonContent.java index 0da5287c73..c4a998b06b 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/content/JsonContent.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/content/JsonContent.java @@ -2,6 +2,8 @@ import im.actor.core.api.ApiJsonMessage; import im.actor.core.entity.content.internal.ContentRemoteContainer; +import im.actor.runtime.json.JSONException; +import im.actor.runtime.json.JSONObject; public class JsonContent extends AbsContent { @@ -16,4 +18,15 @@ public JsonContent(ContentRemoteContainer contentRemoteContainer) { public String getRawJson() { return rawJson; } + + public String getContentDescription() { + String res; + try { + JSONObject data = new JSONObject(getRawJson()); + res = data.getJSONObject("data").getString("text"); + } catch (JSONException e) { + res = ""; + } + return res; + } } From bcb8f4dc136e8841e01aa0282c89751f456e28d2 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Tue, 25 Oct 2016 18:28:18 +0300 Subject: [PATCH 409/414] feat(core): add json content description --- .../src/main/java/im/actor/core/entity/ContentDescription.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/ContentDescription.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/ContentDescription.java index 0723cf7ecf..aa1677b925 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/ContentDescription.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/ContentDescription.java @@ -100,7 +100,7 @@ public static ContentDescription fromContent(AbsContent msg) { return new ContentDescription(ContentType.SERVICE, ((ServiceContent) msg).getCompatText(), 0, false); } else if (msg instanceof JsonContent) { - return new ContentDescription(ContentType.CUSTOM_JSON_MESSAGE, ((JsonContent) msg).getContentDescription()); + return new ContentDescription(ContentType.TEXT, ((JsonContent) msg).getContentDescription()); } else { return new ContentDescription(ContentType.UNKNOWN_CONTENT); } From b1488949ce2c4d62989ee8b8e3e07f352ed7b137 Mon Sep 17 00:00:00 2001 From: rockjam Date: Mon, 31 Oct 2016 19:33:03 +0300 Subject: [PATCH 410/414] feat(bots): allow to add users to group --- .../src/main/scala/im/actor/bots/BotMessages.scala | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/actor-server/actor-bots-shared/src/main/scala/im/actor/bots/BotMessages.scala b/actor-server/actor-bots-shared/src/main/scala/im/actor/bots/BotMessages.scala index 41b25b906c..294e3a7d95 100644 --- a/actor-server/actor-bots-shared/src/main/scala/im/actor/bots/BotMessages.scala +++ b/actor-server/actor-bots-shared/src/main/scala/im/actor/bots/BotMessages.scala @@ -553,11 +553,17 @@ object BotMessages { @key("CreateGroupWithOwner") final case class CreateGroupWithOwner( @beanGetter title: String, - @beanGetter user: UserPeer + @beanGetter user: UserPeer, + members: Seq[UserPeer] ) extends RequestBody { override type Response = ResponseCreateGroup override val service: String = Services.Groups + def this(title: String, user: UserPeer, members: java.util.List[UserPeer]) = + this(title, user, members.toIndexedSeq) + + def getMembers = seqAsJavaList(members) + override def readResponse(obj: Js.Obj): Response = readJs[Response](obj) } From 46a633e33ff337fcc5489b78ff9c4f08640202a0 Mon Sep 17 00:00:00 2001 From: rockjam Date: Mon, 31 Oct 2016 20:00:12 +0300 Subject: [PATCH 411/414] fix(server:bots): method to add members to groups --- .../bot/services/GroupsBotService.scala | 24 +++++++++---------- actor-server/project/Dependencies.scala | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/actor-server/actor-bots/src/main/scala/im/actor/server/bot/services/GroupsBotService.scala b/actor-server/actor-bots/src/main/scala/im/actor/server/bot/services/GroupsBotService.scala index 857e9e7a54..a96984ddc6 100644 --- a/actor-server/actor-bots/src/main/scala/im/actor/server/bot/services/GroupsBotService.scala +++ b/actor-server/actor-bots/src/main/scala/im/actor/server/bot/services/GroupsBotService.scala @@ -23,30 +23,30 @@ private[bot] final class GroupsBotService(system: ActorSystem) extends BotServic private val groupExt = GroupExtension(system) override val handlers: Handlers = { - case CreateGroup(title) ⇒ createGroup(title).toWeak - case CreateGroupWithOwner(title, ownerUserId) ⇒ createGroupWithOwner(title, ownerUserId).toWeak - case UpdateGroupShortName(groupId, shortName) ⇒ updateShortName(groupId, shortName).toWeak - case AddGroupExtString(groupId, key, value) ⇒ addGroupExtString(groupId, key, value).toWeak - case AddGroupExtBool(groupId, key, value) ⇒ addGroupExtBool(groupId, key, value).toWeak - case RemoveGroupExt(groupId, key) ⇒ removeExt(groupId, key).toWeak - case InviteUser(groupPeer, userPeer) ⇒ inviteUser(groupPeer, userPeer).toWeak + case CreateGroup(title) ⇒ createGroup(title).toWeak + case CreateGroupWithOwner(title, owner, members) ⇒ createGroupWithOwner(title, owner, members).toWeak + case UpdateGroupShortName(groupId, shortName) ⇒ updateShortName(groupId, shortName).toWeak + case AddGroupExtString(groupId, key, value) ⇒ addGroupExtString(groupId, key, value).toWeak + case AddGroupExtBool(groupId, key, value) ⇒ addGroupExtBool(groupId, key, value).toWeak + case RemoveGroupExt(groupId, key) ⇒ removeExt(groupId, key).toWeak + case InviteUser(groupPeer, userPeer) ⇒ inviteUser(groupPeer, userPeer).toWeak } private def createGroup(title: String) = RequestHandler[CreateGroup, CreateGroup#Response]( (botUserId: Int, _: Long, _: Int) ⇒ { - create(title, botUserId) + create(title, botUserId, Set.empty) } ) - private def createGroupWithOwner(title: String, userPeer: UserPeer) = RequestHandler[CreateGroup, CreateGroup#Response]( + private def createGroupWithOwner(title: String, owner: UserPeer, members: Seq[UserPeer]) = RequestHandler[CreateGroup, CreateGroup#Response]( (botUserId: Int, _: Long, _: Int) ⇒ { ifIsAdmin(botUserId) { - create(title, userPeer.id) + create(title, owner.id, (members map (_.id)).toSet) } } ) - private def create(title: String, ownerUserId: Int) = { + private def create(title: String, ownerUserId: Int, memberIds: Set[Int]) = { val groupId = IdUtils.nextIntId() val randomId = ThreadLocalRandom.current().nextLong() @@ -57,7 +57,7 @@ private[bot] final class GroupsBotService(system: ActorSystem) extends BotServic clientAuthId = 0L, title = title, randomId = randomId, - userIds = Set.empty + userIds = memberIds ) } yield Right(ResponseCreateGroup(GroupOutPeer(groupId, ack.accessHash))) } diff --git a/actor-server/project/Dependencies.scala b/actor-server/project/Dependencies.scala index b98462b02a..d237fc0ae0 100644 --- a/actor-server/project/Dependencies.scala +++ b/actor-server/project/Dependencies.scala @@ -5,7 +5,7 @@ import sbt._ object Dependencies { object V { val actorCommons = "0.0.20" - val actorBotkit = "1.0.112" + val actorBotkit = "1.0.113" val akka = "2.4.10" val akkaHttpJson = "1.10.0" val cats = "0.7.2" From 185f0e463b3d7de18163db18a1dd438821587834 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Tue, 1 Nov 2016 20:32:55 +0300 Subject: [PATCH 412/414] feat(android): add dialogs fragment to delegate --- .../src/main/java/im/actor/sdk/ActorSDKDelegate.java | 3 +++ .../src/main/java/im/actor/sdk/BaseActorSDKDelegate.java | 6 ++++++ .../java/im/actor/sdk/controllers/root/RootFragment.java | 3 ++- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorSDKDelegate.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorSDKDelegate.java index 6911161276..dc94c1e80e 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorSDKDelegate.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorSDKDelegate.java @@ -15,6 +15,7 @@ import im.actor.sdk.controllers.conversation.mentions.AutocompleteFragment; import im.actor.sdk.controllers.conversation.messages.BubbleLayouter; import im.actor.sdk.controllers.conversation.quote.QuoteFragment; +import im.actor.sdk.controllers.dialogs.DialogsDefaultFragment; import im.actor.sdk.intents.ActorIntent; import im.actor.sdk.intents.ActorIntentFragmentActivity; @@ -217,4 +218,6 @@ public interface ActorSDKDelegate { * @param layouters default layouters */ void configureChatViewHolders(ArrayList layouters); + + DialogsDefaultFragment fragmentForDialogs(); } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/BaseActorSDKDelegate.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/BaseActorSDKDelegate.java index f9725ba295..b4f87298e4 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/BaseActorSDKDelegate.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/BaseActorSDKDelegate.java @@ -16,6 +16,7 @@ import im.actor.sdk.controllers.conversation.mentions.AutocompleteFragment; import im.actor.sdk.controllers.conversation.messages.BubbleLayouter; import im.actor.sdk.controllers.conversation.quote.QuoteFragment; +import im.actor.sdk.controllers.dialogs.DialogsDefaultFragment; import im.actor.sdk.intents.ActorIntent; import im.actor.sdk.intents.ActorIntentFragmentActivity; @@ -158,4 +159,9 @@ public RawUpdatesHandler getRawUpdatesHandler() { @Override public void configureChatViewHolders(ArrayList layouters) { } + + @Override + public DialogsDefaultFragment fragmentForDialogs() { + return null; + } } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/root/RootFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/root/RootFragment.java index 88624562e5..382abab932 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/root/RootFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/root/RootFragment.java @@ -74,8 +74,9 @@ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { if (!isInited) { isInited = true; + DialogsDefaultFragment dialogsDefaultFragment = ActorSDK.sharedActor().getDelegate().fragmentForDialogs(); getChildFragmentManager().beginTransaction() - .add(R.id.content, new DialogsDefaultFragment()) + .add(R.id.content, dialogsDefaultFragment != null ? dialogsDefaultFragment : new DialogsDefaultFragment()) .add(R.id.fab, new ComposeFabFragment()) .add(R.id.search, new GlobalSearchDefaultFragment()) .add(R.id.placeholder, new GlobalPlaceholderFragment()) From 04d225ad480158e3074b8adc023d3d33432e14fd Mon Sep 17 00:00:00 2001 From: gputintsev Date: Sun, 4 Dec 2016 13:41:49 +0300 Subject: [PATCH 413/414] chore(android): add nl translation --- .../src/main/res/values-nl/ui_text.xml | 649 ++++++++++++++++++ 1 file changed, 649 insertions(+) create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/values-nl/ui_text.xml diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-nl/ui_text.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-nl/ui_text.xml new file mode 100644 index 0000000000..79d53e45b9 --- /dev/null +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values-nl/ui_text.xml @@ -0,0 +1,649 @@ + + + + Homecrew + Nl + + Bevestig + Annuleer + Ja + Nee + Probeer opnieuw + + Neem foto + Kies een foto uit de gallerij + Verwijder foto + Ophalen + + Laden… + + Geen SD-kaart gevonden + Telefoonnummer gekopieerd naar klembord + E-mailadres gekopieerd naar klembord + Berichten gekopieerd naar klembord + Gebruikersnaam gekopieerd naar klembord + Kan gebruiker niet uit de groep verwijderen + Kan gebruiker niet aan de groep toevoegen + Gebruiker is al lid van de groep + "Naam" veld mag niet leeg zijn + Naam wijzigen niet geslaagd + "Gebruikersnaam" sectie mag niet leeg zijn + Gebruikersnaam wijzigen niet geslaagd + "Over mij" veld mag niet leeg zijn + Geen applicatie beschikbaar om dit te openen + Kan de groep niet verlaten + Kan de groep niet aanmaken + Kan het gesprek niet verwijderen + Kan het gesprek niet wissen + + Delen + Opslaan naar telefoon + Opgeslagen + Volgende + + + Hoi! Zullen we {appName} gebruiken? Handig voor een veilige en verbonden buurt! {inviteUrl} + Via SMS uitnodigen + Via e-mail uitnodigen + + + REGISTREREN + AANMELDEN + met telefoonnummer + met e-mail + met Google + Naar boven + + Homecrew + + Voorkom inbraak. Buren worden dienstverleners. + + Heel de buurt in een chat + Onze buurtchat ondersteunt meer dan 300 gebruikers in een enkele groep. Communiceer met je buurtgenoten en meld elkaar bijzonderheden. + + Werkt overal + Waar je ook bent in de buurt, thuis of buiten, berichten komen aan bij andere buurtgenoten. + + Beveiligd & besloten + Laat berichten achter voor contactpersonen of in de afgesloten buurtchat. Deel documenten, bestanden en foto\'s zonder zorgen dat ze in de verkeerde handen vallen. Alle gesprekken zijn met encryptie versleuteld. + + + Aanmelden + Selecteer de landcode en voer het telefoonnummer in. + Telefoonnummer + BEVESTIG HET NUMMER + WAAROM HEBBEN WE HET NUMMER NODIG? + We gebruiken dit telefoonnummer voor authenticatie, dit is een veilige manier van aanmelden.\n\nWachtwoorden zijn veelal te makkelijk om te raden en e-mail kan eenvoudig in de verkeerde handen vallen. + Hebbes! + Selecteer een land + Ongeldige landcode + + We gebruiken het e-mailadres om contactpersonen te zoeken en om eventueel automatisch aan groepen toewijzen. + WAAROM HEBBEN WIJ HET E-MAILADRES NODIG? + + Gebruik e-mail om te registreren + Gebruik telefoon om te registreren + + + Kies een soort authorisatie + Kies a.u.b. een authorisatiemethode. + Registeren + Aanmelden + met telefoonnummer + met e-mailadres + + + + Registreren + Geef het e-mailadres op. + E-mail + BEVESTIG E-MAIL + + + Bij gebruik van deze dienst wordt akkoord gegaan met de algemene voorwaarden en het privacybeleid. + Bij gebruik van deze dienst wordt met de algemene voorwaarden akkoord gegaan. + Bij gebruik van deze dienst wordt met het privacybeleid akkoord gegaan. + Andere gebruikers zijn in staat deze account te vinden op basis van telefoonnummer of gebruikersnaam. + + Wijzig server end-point + fabrieksinstellingen herstellen + + Algemene Voorwaarden + Privacybeleid + + + Aanmelden + Voer een telefoonnummer of e-mailadres in. + Geef een telefoonnummer op. + Geef een e-mailadres. + telefoonnummer of e-mailadres + telefoon + e-mail + AANMELDEN + + + We hebben een SMS met de activatiecode naar het telefoonnummer {0} gestuurd + We hebben een e-mail met de activatiecode naar {0} gestuurd + Voer de SMS code in + Code + KLAAR + VERKEERD TELEFOONNUMMER? + VERKEERDE E-MAIL? + Wijzig dit nummer? + Wijzig dit e-mail? + Wijzigen + + + Hoi! Vertel ons je naam + Vertel ons je naam + Je naam + VOLGENDE + + + Geef een geldig telefoonnummer of e-mailadres op + Geef een telefoonnummer op + Geef een e-mailadres op + De code is verlopen. Voer validatieproces opnieuw uit. + De code is ongeldig. Probeer het nog eens. + Authorisatie serverfout. Probeer het nog eens. + + + Typ iets om te zoeken + Homecrew doorzoeken + Niets gevonden + Help + Zoeken + + GESPREKKEN + CONTACTEN + + Opstellen + Maak groep + Maak kanaal + Lid worden van openbare groep + Contactpersoon toevoegen + + Bezig met synchroniseren + Een ogenblik geduld, we zijn de account aan het inrichten. + + Nodig buurtgenoten uit + Geen van de bekende contactpersonen gebruiken {appName}. Gebruik de knop beneden om ze uit te nodigen. + VERTEL EEN VRIEND + of voeg handmatig een contact toe + CONTACT TOEVOEGEN + + + Verstuur mijn contactpersoon… + Mijn telefoonnummer is {0} + Bewerk profiel + Wijzig foto + Account + Over Homecrew + Help + Stel een vraag of geef feedback + Twitter + Home + Meldingen + Gespreksinstellingen + Beveiliging en privacy + Lijst van geblokkeerde contacten + Stel gebruikersnaam in + Wijzig gebruikersnaam + Gebruikersnaam + In {appName} kan een gebruikersnaam gebruikt worden. Anderen zijn dan in staat deze account te vinden door hierop te zoeken en contact op te nemen zonder een telefoonnummer te gebruiken.\n\nHet is toegestaan de (hoofd)letters a–z, de getallen 0–9 en liggende streepjes te gebruiken. De minimum lengte is 5 tekens. + Gebruikersnaam invoeren + Over mij + Over gebruiker + Achtergrondafbeelding + + + Bel {0} + E-mail {0} + SMS {0} + Deel {0} + Kopie + + + Gesprekken + Lang ingedrukt houden voor meer opties + Begin een gesprek + Selecteer een contact uit de lijst of gebruik het pennetje om een conversatie te beginnen. + + Bekijk contactpersoon + Hernoem contactpersoon + Verwijder conversatie + + Bekijk groepsinformatie + Hernoem groep + Verlaat groep + Verwijder groep + + Bekijk informatie over het kanaal + Hernoem kanaal + Verlaat kanaal + Verwijder kanaal + + + Contacten + Geen bestaande contacten gevonden + Vertel buurtgenoten en anderen over {appName} + Voeg iemand toe… + + Nodig buurtgenoten uit + Geen van de bestaande contacten gebruiken {appName}. Gebruik de knop hieronder om ze uit te nodigen. + Vertel iemand over ons + of voeg handmatig contacten toe met de plus knop hieronder. + + Verwijder {0} uit mijn contacten + Verwijderen… + Bewerk naam + + Uitnodigen via link + + Alles selecteren + Alles deselecteren + + Deel de link + Stuur de link via SMS/e-mail naar meerdere contacten + + + Groepsinformatie + Bekijk contactinformatie + Bellen + Bestanden + Gespreksgeschiedenis wissen + Groep verlaten + + Plaatsen + Contact + Bestand + Video opnemen + Foto maken + Gallerij + + Het bericht… + + Nieuwe berichten + Aan contacten toevoegen + Contact toevoegen… + + Openen + Verzenden + Stoppen + Downloaden + VEEG OM TE ANNULEREN + + Geen groepslidmaatschap gevonden + Groep verwijderd + Kanaal verwijderd + + Lid worden + Microfoon uit + Microfoon aan + + START + Nog geen beschrijving van deze bot + + Het bericht wordt nog niet ondersteund in deze versie. Even geduld tot de applicatie bijgewerkt is om het bericht te zien. + \u25CF online + + Doorsturen + Citeren + Verwijderen + Kopieer + Vind ik leuk + Bewerk laatste bericht + Alleen het meest recente bericht kan bewerkt worden + Het bericht is te oud + + + Gallerij + Video + Bestand + Camera + Contact + Locatie + + + Locatie + Kaart + Satelliet + Hybride + + + Profiel + Gedeeld + Documenten + Media + Instellingen + Meldingen + Geluidsmeldingen + Blokkeer gebruiker + Blokkering opheffen + Blokkeer {user}? + + Verwijder uit contacten + Voeg toe aan contactpersonen + Oproep + Videogesprek + Nieuw bericht + + Mobiele telefoon + + Aan lijst met contactpersonen toe aan het voegen… + Verwijderen uit lijst met contactpersonen… + + Hernoem + Bel + Verwijder uit contactpersonen + Voeg toe aan contactpersonen + Beveiliging + Kan gebruiker niet gevonden krijgen + + + Groepsinformatie + Informatie over het kanaal + Leden + {0} van {1} + + beheerder + + Beheerd door {0} + Door mij beheerd + + Aan groep toevoegen… + Uit groep verwijderen… + + Bericht {0} + Bellen {0} + Bekijken {0} + Verwijder {0} uit de groep + + Bewerk + Wijzig thema + Wijzig profielfoto + Token voor andere diensten + + Gedeeld + Documenten + + Voeg een lid toe + Voeg een lid toe + Leden toevoegen + Groep verlaten + Kanaal verlaten + Beheer + Beheerder maken + Beheerdersrechten intrekken + + Beheer groep + Beheer kanaal + + Systeemrechten + Beheer wat mogelijk is in deze groep + Beheer wat mogelijk is in dit kanaal + + Soort groep + Soort kanaal + Openbaar + Openbaar + Besloten + Besloten + Verwijder groep + Alle berichten uit deze groep zullen verloren gaan. + Kanaal verwijderen + Alle berichten uit dit kanaal zullen verloren gaan. + + Deel gespreksgeschiedenis + Gedeeld + Alle leden zullen alle berichten uit de geschiedenis kunnen zien + + Alle leden kunnen groepsinformatie wijzigen + Alleen beheerders kunnen groepsinformatie wijzigen + Alle leden kunnen uitnodingen versturen + Laat bericht zien wanneer iemand de groep binnen komt en verlaat + Label van beheerder is voor iedereen zichtbaar + + Alle leden kunnen informatie over het kanaal wijzigen + Alleen beheerders kunnen informatie over het kanaal wijzigen + Alle leden kunnen uitnodigingen versturen + Laat bericht zien wanneer iemand het kanaal binnen komt en verlaat + Label van beheerder is voor iedereen zichtbaar + + Openbare groep + Openbare groepen kunnen gezocht en gevonden worden, iedereen kan lid worden. + Besloten groep + Bij besloten groepen kan alleen via een uitnodiging het lidmaatschap verkregen worden. + + Openbaar kanaal + Openbare groepen kunnen gezocht en gevonden worden, iedereen kan lid worden. + Besloten kanaal + Bij besloten kanalen kan alleen via een uitnodiging het lidmaatschap verkregen worden. + + + Geen lid van deze groep + Over deze groep + Groepsbeschrijving instellen + Groepsonderwerp + Groepsonderwerp instellen + + bot + + Korte groepsnaam kon niet gewijzigd worden + + + + Profielfoto + Foto + Groepsafbeelding + Geen afbeelding + Bewerken + Instellen als… + + + Contact toevoegen + Geef een e-mail, gebruikersnaam of telefoonnummer op + ANNULEER + VERDER + + + Nieuw bericht + + + Wijzig groep + Naam van de groep + Geef een groepsonderwerp op en stel eventueel een afbeelding in + Zoek op naam + Klaar + + + Maak kanaal + Naam van het kanaal + Geef een naam van het kanaal op en stel eventueel een afbeelding in + + Naam + Beschrijving + + + Lid toevoegen + + + Uitnodiging + Iemand die {appName} geinstalleerd heeft zal toegang tot de groep kunnen krijgen door de link te gebruiken + Kopieer link + Intrekken + Delen + Link gekopieerd naar klembord + Groepsuitnodiging via link + Niet in staat link te maken + Niet in staat link in te trekken + Uitnodiging link laden + + Lid worden van "%1$s"? + + + Documenten + Nog geen documenten + Bestand opgeslagen: + + + Geef een naam op + Geef een naam van de contactpersoon + Geef een groepsnaam op + Geef een groepsonderwerp op + Geef een gebruikersnaam op + ANNULEER + OPSLAAN + Wijzig naam… + Gebruikersnaam wijzigen… + Beschrijving wijzigen… + Groepsonderwerp wijzigen… + Vertel mensen iets kort. + + + + Help + Over + Over de app en onze diensten + FAQ + Veelgestelde vragen + Feedback + Geef ons terugkoppeling + Applicatie versie + App versie naar klembord gekopieerd + Stuur e-mail + Feedback + + + Meldingen + Gespreksgeluiden + Speel geluiden af bij in- en uitgaande berichten. + + Trillen + Trillen bij meldingen inschakelen + Groep + Inschakelen groepsmeldingen + Alleen wanneer ik direct aangesproken word door iemand + Groepsmeldingen alleen bij direct aanspreken inschakelen + Geef namen en berichten weer + Geef berichttekst en afzender weer in meldingenvenster + Geluid + Inschakelen geluiden bij meldingen + Geluid instellen + Geluidsmelding instellen + " berichten in " + " gesprekken" + " berichten" + + + Kies iemand + + + Gespreksinstellingen + Versturen bij [Enter] + Gebruik van de [Enter] toets zal een bericht verzenden in plaats van een nieuwe regel + + Automatisch animatie afspelen + Na downloaden van animaties deze gelijk automatisch afspelen + + Markdown + Of markdown inschakeld is + + Automatisch animaties ophalen + Automatisch afbeeldingen ophalen + Automatisch videos ophalen + Automatisch geluidsopnames ophalen + Automatisch documenten ophalen + + + Beveiliging en privacy + Alle sessies beëindigen + Op alle apparaten afmelden + Op alle andere apparaten afmelden? Alle tijdelijke applicatiegegevens op die apparaten zal verloren gaan. + Dit apparaat afmelden? Alle lokale applicatiegegevens zullen verloren gaan op {device} + Gevalideerde apparaten en diensten + Laden… + Niet in staat authorisatie in te trekken + (Dit) + Niet in staat te laden. Probeer het opnieuw. + + + Voor het laatst gezien + Wijzig wie kan zien wanneer ik voor het laatst online was. + Iedereen + Mijn contactpersonen + Niemand + + + Lijst met geblokkeerde contacten + Er wordt niemand geblokkeerd + Lijst met blokkeringen laden… + OPHEFFEN + + + + Definitief {0} berichten verwijderen? + Verwijder + + Definitief ALLE berichten uit dit gesprek wissen? + Verwijder + + Verwijder {0} contactpersonen? Zeker weten? + Verwijder + + Toevoegen van {0} aan de groep? + Toevoegen + + Verwijderen van {0} uit de groep? + Verwijder + + "{0}" staat nog niet geregistreerd in {appName}. Uitnodiging voor {appName} versturen? + Uitnodigen + + + Media + Afbeelding + %d van %d + geen recente emoji gevonden + + + Oproep beëindigd + Binnenkomende oproep + Gaat over… + Verbinden… + geluidsspeaker + microfoon uitzetten + video + berichten + gebruiker toevoegen + terug naar oproep + + + Groep "%1$s" verlaten? Zeker weten? + Kanaal "%1$s" verlaten? Zeker weten? + Verlaten + + Verwijder "%1$s" groep? + Verwijder "%1$s" kanaal? + Verwijder + + Verwijder gesprek met "%1$s"? + Verwijder + + Intrekken link? + Intrekken + + Intrekken token? + Intekken + + Deel met + + + Poging verbinding te maken mislukt. Verzeker dat er een werkende internetverbinding is en probeer het opnieuw.\n\nAls de problemen aanhouden, probeer dan of de telefoon opnieuw opstarten helpt. + Niet mogelijk de actie uit te voeren.\n\nControleer het toestel en herstart de telefoon indien problemen aanhouden. + Onbekend URL type + + From 5123c1584757c6eeea0ed2a0e7e043629871a0c6 Mon Sep 17 00:00:00 2001 From: gputintsev Date: Sun, 4 Dec 2016 13:43:05 +0300 Subject: [PATCH 414/414] fix(android): clear about --- .../im/actor/sdk/controllers/settings/EditAboutFragment.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/EditAboutFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/EditAboutFragment.java index 65e49b71bb..414ddadba2 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/EditAboutFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/settings/EditAboutFragment.java @@ -76,8 +76,7 @@ public void onClick(View v) { public void onClick(View v) { String about = aboutEdit.getText().toString().trim(); if (about.length() == 0) { - Toast.makeText(getActivity(), R.string.toast_empty_about, Toast.LENGTH_SHORT).show(); - return; + about = null; } if (type == EditAboutActivity.TYPE_ME) {