From 05586ccf47c8bfc24499fd6c06fe87543200a3f9 Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Tue, 28 Dec 2021 15:10:29 +0100 Subject: [PATCH] add user status option to account dialog (WIP) Signed-off-by: Marcel Hibbe --- .../java/com/nextcloud/talk/api/NcApi.java | 7 + .../models/database/CapabilitiesUtil.java | 18 +- .../json/capabilities/Capabilities.java | 160 +++++++ .../capabilities/UserStatusCapability.java | 32 ++ .../talk/models/json/status/Status.java | 159 ++++++ .../talk/models/json/status/StatusOCS.java | 69 +++ .../models/json/status/StatusOverall.java | 64 +++ .../dialog/ChooseAccountDialogFragment.java | 117 +++-- .../talk/ui/dialog/SetStatusDialogFragment.kt | 420 ++++++++++++++++ .../com/nextcloud/talk/utils/ApiUtils.java | 8 + app/src/main/res/drawable/ic_edit.xml | 34 ++ .../main/res/drawable/ic_user_status_away.xml | 32 ++ .../main/res/drawable/ic_user_status_dnd.xml | 38 ++ .../res/drawable/ic_user_status_invisible.xml | 34 ++ app/src/main/res/drawable/online_status.xml | 23 + .../main/res/layout/dialog_choose_account.xml | 39 +- app/src/main/res/layout/dialog_set_status.xml | 453 ++++++++++++++++++ app/src/main/res/layout/predefined_status.xml | 62 +++ app/src/main/res/values-night/colors.xml | 2 + app/src/main/res/values/colors.xml | 7 + app/src/main/res/values/dimens.xml | 6 + app/src/main/res/values/strings.xml | 21 + app/src/main/res/values/styles.xml | 8 + 23 files changed, 1781 insertions(+), 32 deletions(-) create mode 100644 app/src/main/java/com/nextcloud/talk/models/json/capabilities/Capabilities.java create mode 100644 app/src/main/java/com/nextcloud/talk/models/json/capabilities/UserStatusCapability.java create mode 100644 app/src/main/java/com/nextcloud/talk/models/json/status/Status.java create mode 100644 app/src/main/java/com/nextcloud/talk/models/json/status/StatusOCS.java create mode 100644 app/src/main/java/com/nextcloud/talk/models/json/status/StatusOverall.java create mode 100644 app/src/main/java/com/nextcloud/talk/ui/dialog/SetStatusDialogFragment.kt create mode 100644 app/src/main/res/drawable/ic_edit.xml create mode 100644 app/src/main/res/drawable/ic_user_status_away.xml create mode 100644 app/src/main/res/drawable/ic_user_status_dnd.xml create mode 100644 app/src/main/res/drawable/ic_user_status_invisible.xml create mode 100644 app/src/main/res/drawable/online_status.xml create mode 100644 app/src/main/res/layout/dialog_set_status.xml create mode 100644 app/src/main/res/layout/predefined_status.xml diff --git a/app/src/main/java/com/nextcloud/talk/api/NcApi.java b/app/src/main/java/com/nextcloud/talk/api/NcApi.java index 6f61c6f97..74c17649a 100644 --- a/app/src/main/java/com/nextcloud/talk/api/NcApi.java +++ b/app/src/main/java/com/nextcloud/talk/api/NcApi.java @@ -40,6 +40,7 @@ import com.nextcloud.talk.models.json.push.PushRegistrationOverall; import com.nextcloud.talk.models.json.search.ContactsByNumberOverall; import com.nextcloud.talk.models.json.signaling.SignalingOverall; import com.nextcloud.talk.models.json.signaling.settings.SignalingSettingsOverall; +import com.nextcloud.talk.models.json.status.StatusOverall; import com.nextcloud.talk.models.json.userprofile.UserProfileFieldsOverall; import com.nextcloud.talk.models.json.userprofile.UserProfileOverall; @@ -443,4 +444,10 @@ public interface NcApi { @GET Observable getOpenConversations(@Header("Authorization") String authorization, @Url String url); + + /* + * OCS Status API + */ + @GET + Observable status(@Header("Authorization") String authorization, @Url String url); } diff --git a/app/src/main/java/com/nextcloud/talk/models/database/CapabilitiesUtil.java b/app/src/main/java/com/nextcloud/talk/models/database/CapabilitiesUtil.java index b5ba0a04b..3d8375bcc 100644 --- a/app/src/main/java/com/nextcloud/talk/models/database/CapabilitiesUtil.java +++ b/app/src/main/java/com/nextcloud/talk/models/database/CapabilitiesUtil.java @@ -56,7 +56,7 @@ public abstract class CapabilitiesUtil { Capabilities capabilities = LoganSquare.parse(user.getCapabilities(), Capabilities.class); if (capabilities.getExternalCapability() != null && capabilities.getExternalCapability().containsKey("v1")) { - return capabilities.getExternalCapability().get("v1").contains("capabilityName"); + return capabilities.getExternalCapability().get("v1").contains(capabilityName); } } catch (IOException e) { Log.e(TAG, "Failed to get capabilities for the user"); @@ -175,6 +175,22 @@ public abstract class CapabilitiesUtil { return false; } + public static boolean isUserStatusAvailable(@Nullable UserEntity user) { + if (user != null && user.getCapabilities() != null) { + try { + Capabilities capabilities = LoganSquare.parse(user.getCapabilities(), Capabilities.class); + if (capabilities.getUserStatusCapability() != null && + capabilities.getUserStatusCapability().isEnabled() && + capabilities.getUserStatusCapability().isSupportsEmoji()) { + return true; + } + } catch (IOException e) { + Log.e(TAG, "Failed to get capabilities for the user"); + } + } + return false; + } + public static String getAttachmentFolder(@Nullable UserEntity user) { if (user != null && user.getCapabilities() != null) { try { diff --git a/app/src/main/java/com/nextcloud/talk/models/json/capabilities/Capabilities.java b/app/src/main/java/com/nextcloud/talk/models/json/capabilities/Capabilities.java new file mode 100644 index 000000000..ca0fa9492 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/models/json/capabilities/Capabilities.java @@ -0,0 +1,160 @@ +/* + * Nextcloud Talk application + * + * @author Mario Danic + * Copyright (C) 2017-2018 Mario Danic + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.nextcloud.talk.models.json.capabilities; + +import com.bluelinelabs.logansquare.annotation.JsonField; +import com.bluelinelabs.logansquare.annotation.JsonObject; + +import org.parceler.Parcel; + +import java.util.HashMap; +import java.util.List; + +@Parcel +@JsonObject +public class Capabilities { + @JsonField(name = "spreed") + SpreedCapability spreedCapability; + + @JsonField(name = "notifications") + NotificationsCapability notificationsCapability; + + @JsonField(name = "theming") + ThemingCapability themingCapability; + + @JsonField(name = "external") + HashMap> externalCapability; + + @JsonField(name = "provisioning_api") + ProvisioningCapability provisioningCapability; + + @JsonField(name = "user_status") + UserStatusCapability userStatusCapability; + + public SpreedCapability getSpreedCapability() { + return this.spreedCapability; + } + + public NotificationsCapability getNotificationsCapability() { + return this.notificationsCapability; + } + + public ThemingCapability getThemingCapability() { + return this.themingCapability; + } + + public HashMap> getExternalCapability() { + return this.externalCapability; + } + + public ProvisioningCapability getProvisioningCapability() { + return this.provisioningCapability; + } + + public UserStatusCapability getUserStatusCapability() { + return userStatusCapability; + } + + public void setSpreedCapability(SpreedCapability spreedCapability) { + this.spreedCapability = spreedCapability; + } + + public void setNotificationsCapability(NotificationsCapability notificationsCapability) { + this.notificationsCapability = notificationsCapability; + } + + public void setThemingCapability(ThemingCapability themingCapability) { + this.themingCapability = themingCapability; + } + + public void setExternalCapability(HashMap> externalCapability) { + this.externalCapability = externalCapability; + } + + public void setProvisioningCapability(ProvisioningCapability provisioningCapability) { + this.provisioningCapability = provisioningCapability; + } + + public void setUserStatusCapability(UserStatusCapability userStatusCapability) { + this.userStatusCapability = userStatusCapability; + } + + public boolean equals(final Object o) { + if (o == this) { + return true; + } + if (!(o instanceof Capabilities)) { + return false; + } + final Capabilities other = (Capabilities) o; + if (!other.canEqual((Object) this)) { + return false; + } + final Object this$spreedCapability = this.getSpreedCapability(); + final Object other$spreedCapability = other.getSpreedCapability(); + if (this$spreedCapability == null ? other$spreedCapability != null : !this$spreedCapability.equals(other$spreedCapability)) { + return false; + } + final Object this$notificationsCapability = this.getNotificationsCapability(); + final Object other$notificationsCapability = other.getNotificationsCapability(); + if (this$notificationsCapability == null ? other$notificationsCapability != null : !this$notificationsCapability.equals(other$notificationsCapability)) { + return false; + } + final Object this$themingCapability = this.getThemingCapability(); + final Object other$themingCapability = other.getThemingCapability(); + if (this$themingCapability == null ? other$themingCapability != null : !this$themingCapability.equals(other$themingCapability)) { + return false; + } + final Object this$externalCapability = this.getExternalCapability(); + final Object other$externalCapability = other.getExternalCapability(); + if (this$externalCapability == null ? other$externalCapability != null : !this$externalCapability.equals(other$externalCapability)) { + return false; + } + final Object this$provisioningCapability = this.getProvisioningCapability(); + final Object other$provisioningCapability = other.getProvisioningCapability(); + + return this$provisioningCapability == null ? other$provisioningCapability == null : this$provisioningCapability.equals(other$provisioningCapability); + } + + protected boolean canEqual(final Object other) { + return other instanceof Capabilities; + } + + public int hashCode() { + final int PRIME = 59; + int result = 1; + final Object $spreedCapability = this.getSpreedCapability(); + result = result * PRIME + ($spreedCapability == null ? 43 : $spreedCapability.hashCode()); + final Object $notificationsCapability = this.getNotificationsCapability(); + result = result * PRIME + ($notificationsCapability == null ? 43 : $notificationsCapability.hashCode()); + final Object $themingCapability = this.getThemingCapability(); + result = result * PRIME + ($themingCapability == null ? 43 : $themingCapability.hashCode()); + final Object $externalCapability = this.getExternalCapability(); + result = result * PRIME + ($externalCapability == null ? 43 : $externalCapability.hashCode()); + final Object $provisioningCapability = this.getProvisioningCapability(); + result = result * PRIME + ($provisioningCapability == null ? 43 : $provisioningCapability.hashCode()); + return result; + } + + public String toString() { + return "Capabilities(spreedCapability=" + this.getSpreedCapability() + ", notificationsCapability=" + this.getNotificationsCapability() + ", themingCapability=" + this.getThemingCapability() + ", externalCapability=" + this.getExternalCapability() + ", provisioningCapability=" + this.getProvisioningCapability() + ")"; + } +} diff --git a/app/src/main/java/com/nextcloud/talk/models/json/capabilities/UserStatusCapability.java b/app/src/main/java/com/nextcloud/talk/models/json/capabilities/UserStatusCapability.java new file mode 100644 index 000000000..9c344e852 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/models/json/capabilities/UserStatusCapability.java @@ -0,0 +1,32 @@ +package com.nextcloud.talk.models.json.capabilities; + +import com.bluelinelabs.logansquare.annotation.JsonField; +import com.bluelinelabs.logansquare.annotation.JsonObject; + +import org.parceler.Parcel; + +@Parcel +@JsonObject +public class UserStatusCapability { + @JsonField(name = "enabled") + boolean enabled; + + @JsonField(name = "supports_emoji") + boolean supportsEmoji; + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public boolean isSupportsEmoji() { + return supportsEmoji; + } + + public void setSupportsEmoji(boolean supportsEmoji) { + this.supportsEmoji = supportsEmoji; + } +} diff --git a/app/src/main/java/com/nextcloud/talk/models/json/status/Status.java b/app/src/main/java/com/nextcloud/talk/models/json/status/Status.java new file mode 100644 index 000000000..d6f17d220 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/models/json/status/Status.java @@ -0,0 +1,159 @@ +/* + * + * Nextcloud Talk application + * + * @author Tim Krüger + * Copyright (C) 2021 Tim Krüger + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.nextcloud.talk.models.json.status; + +import com.bluelinelabs.logansquare.annotation.JsonField; +import com.bluelinelabs.logansquare.annotation.JsonObject; + +import org.parceler.Parcel; + +import java.util.Objects; + +@Parcel +@JsonObject +public class Status { + + @JsonField(name = "userId") + public String userId; + + @JsonField(name = "message") + public String message; + + // TODO: Change to enum + @JsonField(name = "messageId") + public String messageId; + + @JsonField(name = "messageIsPredefined") + public boolean messageIsPredefined; + + @JsonField(name = "icon") + public String icon; + + @JsonField(name = "clearAt") + public long clearAt; + + // TODO: Change to enum + @JsonField(name = "status") + public String status; + + @JsonField(name = "statusIsUserDefined") + public boolean statusIsUserDefined; + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getMessageId() { + return messageId; + } + + public void setMessageId(String messageId) { + this.messageId = messageId; + } + + public boolean isMessageIsPredefined() { + return messageIsPredefined; + } + + public void setMessageIsPredefined(boolean messageIsPredefined) { + this.messageIsPredefined = messageIsPredefined; + } + + public String getIcon() { + return icon; + } + + public void setIcon(String icon) { + this.icon = icon; + } + + public long getClearAt() { + return clearAt; + } + + public void setClearAt(long clearAt) { + this.clearAt = clearAt; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public boolean isStatusIsUserDefined() { + return statusIsUserDefined; + } + + public void setStatusIsUserDefined(boolean statusIsUserDefined) { + this.statusIsUserDefined = statusIsUserDefined; + } + + public String getUserId() { + return this.userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Status status1 = (Status) o; + return messageIsPredefined == status1.messageIsPredefined && + clearAt == status1.clearAt && + statusIsUserDefined == status1.statusIsUserDefined && + Objects.equals(userId, status1.userId) && Objects.equals(message, status1.message) && + Objects.equals(messageId, status1.messageId) && Objects.equals(icon, status1.icon) && + Objects.equals(status, status1.status); + } + + @Override + public int hashCode() { + return Objects.hash(userId, message, messageId, messageIsPredefined, icon, clearAt, status, statusIsUserDefined); + } + + @Override + public String toString() { + return "Status{" + + "userId='" + userId + '\'' + + ", message='" + message + '\'' + + ", messageId='" + messageId + '\'' + + ", messageIsPredefined=" + messageIsPredefined + + ", icon='" + icon + '\'' + + ", clearAt=" + clearAt + + ", status='" + status + '\'' + + ", statusIsUserDefined=" + statusIsUserDefined + + '}'; + } +} diff --git a/app/src/main/java/com/nextcloud/talk/models/json/status/StatusOCS.java b/app/src/main/java/com/nextcloud/talk/models/json/status/StatusOCS.java new file mode 100644 index 000000000..620e1084a --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/models/json/status/StatusOCS.java @@ -0,0 +1,69 @@ +/* + * + * Nextcloud Talk application + * + * @author Tim Krüger + * Copyright (C) 2021 Tim Krüger + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.nextcloud.talk.models.json.status; + +import com.bluelinelabs.logansquare.annotation.JsonField; +import com.bluelinelabs.logansquare.annotation.JsonObject; +import com.nextcloud.talk.models.json.generic.GenericOCS; + +import java.util.Objects; + +@JsonObject +public class StatusOCS extends GenericOCS { + @JsonField(name = "data") + public Status data; + + public Status getData() { + return this.data; + } + + public void setData(Status data) { + this.data = data; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { + return false; + } + StatusOCS that = (StatusOCS) o; + return Objects.equals(data, that.data); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), data); + } + + @Override + public String toString() { + return "StatusOCS{" + + "data=" + data + + '}'; + } + +} diff --git a/app/src/main/java/com/nextcloud/talk/models/json/status/StatusOverall.java b/app/src/main/java/com/nextcloud/talk/models/json/status/StatusOverall.java new file mode 100644 index 000000000..1107fc91c --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/models/json/status/StatusOverall.java @@ -0,0 +1,64 @@ +/* + * + * Nextcloud Talk application + * + * @author Tim Krüger + * Copyright (C) 2021 Tim Krüger + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.nextcloud.talk.models.json.status; + +import com.bluelinelabs.logansquare.annotation.JsonField; +import com.bluelinelabs.logansquare.annotation.JsonObject; + +import java.util.Objects; + +@JsonObject +public class StatusOverall { + @JsonField(name = "ocs") + public StatusOCS ocs; + + public StatusOCS getOcs() { + return this.ocs; + } + + public void setOcs(StatusOCS ocs) { + this.ocs = ocs; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + StatusOverall that = (StatusOverall) o; + return Objects.equals(ocs, that.ocs); + } + + @Override + public int hashCode() { + return Objects.hash(ocs); + } + + @Override + public String toString() { + return "StatusOverall{" + + "ocs=" + ocs + + '}'; + } +} diff --git a/app/src/main/java/com/nextcloud/talk/ui/dialog/ChooseAccountDialogFragment.java b/app/src/main/java/com/nextcloud/talk/ui/dialog/ChooseAccountDialogFragment.java index 2e72e943c..e497972e7 100644 --- a/app/src/main/java/com/nextcloud/talk/ui/dialog/ChooseAccountDialogFragment.java +++ b/app/src/main/java/com/nextcloud/talk/ui/dialog/ChooseAccountDialogFragment.java @@ -40,11 +40,14 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.nextcloud.talk.R; import com.nextcloud.talk.activities.MainActivity; import com.nextcloud.talk.adapters.items.AdvancedUserItem; +import com.nextcloud.talk.api.NcApi; import com.nextcloud.talk.application.NextcloudTalkApplication; import com.nextcloud.talk.databinding.DialogChooseAccountBinding; +import com.nextcloud.talk.models.database.CapabilitiesUtil; import com.nextcloud.talk.models.database.User; import com.nextcloud.talk.models.database.UserEntity; import com.nextcloud.talk.models.json.participants.Participant; +import com.nextcloud.talk.models.json.status.StatusOverall; import com.nextcloud.talk.utils.ApiUtils; import com.nextcloud.talk.utils.DisplayUtils; import com.nextcloud.talk.utils.database.user.UserUtils; @@ -61,8 +64,11 @@ import androidx.recyclerview.widget.LinearLayoutManager; import autodagger.AutoInjector; import eu.davidea.flexibleadapter.FlexibleAdapter; import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager; +import io.reactivex.Observable; import io.reactivex.Observer; +import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; +import io.reactivex.schedulers.Schedulers; @AutoInjector(NextcloudTalkApplication.class) public class ChooseAccountDialogFragment extends DialogFragment { @@ -74,6 +80,9 @@ public class ChooseAccountDialogFragment extends DialogFragment { @Inject CookieManager cookieManager; + @Inject + NcApi ncApi; + private DialogChooseAccountBinding binding; private View dialogView; @@ -106,24 +115,27 @@ public class ChooseAccountDialogFragment extends DialogFragment { binding.currentAccount.account.setText((Uri.parse(user.getBaseUrl()).getHost())); if (user.getBaseUrl() != null && - (user.getBaseUrl().startsWith("http://") || user.getBaseUrl().startsWith("https://"))) { + (user.getBaseUrl().startsWith("http://") || user.getBaseUrl().startsWith("https://"))) { binding.currentAccount.userIcon.setVisibility(View.VISIBLE); DraweeController draweeController = Fresco.newDraweeControllerBuilder() - .setOldController(binding.currentAccount.userIcon.getController()) - .setAutoPlayAnimations(true) - .setImageRequest(DisplayUtils.getImageRequestForUrl( - ApiUtils.getUrlForAvatarWithName( - user.getBaseUrl(), - user.getUserId(), - R.dimen.small_item_height), - null)) - .build(); + .setOldController(binding.currentAccount.userIcon.getController()) + .setAutoPlayAnimations(true) + .setImageRequest(DisplayUtils.getImageRequestForUrl( + ApiUtils.getUrlForAvatarWithName( + user.getBaseUrl(), + user.getUserId(), + R.dimen.small_item_height), + null)) + .build(); binding.currentAccount.userIcon.setController(draweeController); } else { binding.currentAccount.userIcon.setVisibility(View.INVISIBLE); } + + + loadCurrentStatus(user); } // Creating listeners for quick-actions @@ -140,6 +152,16 @@ public class ChooseAccountDialogFragment extends DialogFragment { }); } + binding.setStatus.setOnClickListener(v -> { + dismiss(); + SetStatusDialogFragment setStatusDialog = SetStatusDialogFragment.newInstance(user); + setStatusDialog.show(getActivity().getSupportFragmentManager(), "fragment_set_status"); + }); + + if (CapabilitiesUtil.isUserStatusAvailable(userUtils.getCurrentUser())) { + binding.statusView.setVisibility(View.VISIBLE); + } + if (adapter == null) { adapter = new FlexibleAdapter<>(userItems, getActivity(), false); @@ -171,6 +193,39 @@ public class ChooseAccountDialogFragment extends DialogFragment { prepareViews(); } + private void loadCurrentStatus(User user) { + String credentials = ApiUtils.getCredentials(user.getUsername(), user.getToken()); + ncApi.status(credentials, ApiUtils.getUrlForStatus(user.getBaseUrl())). + subscribeOn(Schedulers.io()). + observeOn(AndroidSchedulers.mainThread()). + subscribe(new Observer() { + + private StatusOverall statusOverall; + + @Override + public void onSubscribe(@NonNull Disposable d) { + Log.d("x", "onSubscribe"); + } + + @Override + public void onNext(@NonNull StatusOverall statusOverall) { + Log.d("x", "onNext"); + this.statusOverall = statusOverall; + } + + @Override + public void onError(@NonNull Throwable e) { + Log.e("x", "Läuft net", e); + } + + @Override + public void onComplete() { + Log.d("x", "complete"); + + } + }); + } + private void prepareViews() { if (getActivity() != null) { LinearLayoutManager layoutManager = new SmoothScrollLinearLayoutManager(getActivity()); @@ -196,21 +251,21 @@ public class ChooseAccountDialogFragment extends DialogFragment { } private final FlexibleAdapter.OnItemClickListener onSwitchItemClickListener = - new FlexibleAdapter.OnItemClickListener() { - @Override - public boolean onItemClick(View view, int position) { - if (userItems.size() > position) { - UserEntity userEntity = (userItems.get(position)).getEntity(); - userUtils.createOrUpdateUser(null, - null, - null, - null, - null, - Boolean.TRUE, - null, userEntity.getId(), - null, - null, - null) + new FlexibleAdapter.OnItemClickListener() { + @Override + public boolean onItemClick(View view, int position) { + if (userItems.size() > position) { + UserEntity userEntity = (userItems.get(position)).getEntity(); + userUtils.createOrUpdateUser(null, + null, + null, + null, + null, + Boolean.TRUE, + null, userEntity.getId(), + null, + null, + null) .subscribe(new Observer() { @Override public void onSubscribe(@io.reactivex.annotations.NonNull Disposable d) { @@ -223,7 +278,7 @@ public class ChooseAccountDialogFragment extends DialogFragment { userUtils.disableAllUsersWithoutId(userEntity.getId()); if (getActivity() != null) { getActivity().runOnUiThread( - () -> ((MainActivity) getActivity()).resetConversationsList()); + () -> ((MainActivity) getActivity()).resetConversationsList()); } dismiss(); } @@ -238,9 +293,11 @@ public class ChooseAccountDialogFragment extends DialogFragment { // DONE } }); - } + } + + return true; + } + }; + - return true; - } - }; } diff --git a/app/src/main/java/com/nextcloud/talk/ui/dialog/SetStatusDialogFragment.kt b/app/src/main/java/com/nextcloud/talk/ui/dialog/SetStatusDialogFragment.kt new file mode 100644 index 000000000..6ed134c93 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/ui/dialog/SetStatusDialogFragment.kt @@ -0,0 +1,420 @@ +/* + * Nextcloud Android client application + * + * @author Tobias Kaminsky + * Copyright (C) 2020 Nextcloud GmbH + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this program. If not, see . + */ + +package com.nextcloud.talk.ui.dialog + +import android.annotation.SuppressLint +import android.app.Dialog +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.appcompat.app.AlertDialog +import androidx.fragment.app.DialogFragment +import com.nextcloud.talk.databinding.DialogSetStatusBinding +import com.nextcloud.talk.models.database.User +import com.nextcloud.talk.models.json.status.StatusOverall +import com.vanniktech.emoji.EmojiManager +import com.vanniktech.emoji.google.GoogleEmojiProvider + +private const val ARG_CURRENT_USER_PARAM = "currentUser" +private const val ARG_CURRENT_STATUS_PARAM = "currentStatus" + +private const val POS_DONT_CLEAR = 0 +private const val POS_HALF_AN_HOUR = 1 +private const val POS_AN_HOUR = 2 +private const val POS_FOUR_HOURS = 3 +private const val POS_TODAY = 4 +private const val POS_END_OF_WEEK = 5 + +private const val ONE_SECOND_IN_MILLIS = 1000 +private const val ONE_MINUTE_IN_SECONDS = 60 +private const val THIRTY_MINUTES = 30 +private const val FOUR_HOURS = 4 +private const val LAST_HOUR_OF_DAY = 23 +private const val LAST_MINUTE_OF_HOUR = 59 +private const val LAST_SECOND_OF_MINUTE = 59 + +class SetStatusDialogFragment : + DialogFragment() { + + private lateinit var binding: DialogSetStatusBinding + + // private var currentUser: User? = null + // private var currentStatus: Status? = null + // private lateinit var accountManager: UserAccountManager + // private lateinit var predefinedStatus: ArrayList + // private lateinit var adapter: PredefinedStatusListAdapter + // private var selectedPredefinedMessageId: String? = null + // private var clearAt: Long? = -1 + // private lateinit var popup: EmojiPopup + // + // @Inject + // lateinit var arbitraryDataProvider: ArbitraryDataProvider + // + // @Inject + // lateinit var asyncRunner: AsyncRunner + // + // @Inject + // lateinit var clientFactory: ClientFactory + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + arguments?.let { + // currentUser = it.getParcelable(ARG_CURRENT_USER_PARAM) + // currentStatus = it.getParcelable(ARG_CURRENT_STATUS_PARAM) + + // val json = arbitraryDataProvider.getValue(currentUser, ArbitraryDataProvider.PREDEFINED_STATUS) + + // if (json.isNotEmpty()) { + // val myType = object : TypeToken>() {}.type + // predefinedStatus = Gson().fromJson(json, myType) + // } + } + + + + EmojiManager.install(GoogleEmojiProvider()) + } + + @SuppressLint("InflateParams") + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + binding = DialogSetStatusBinding.inflate(LayoutInflater.from(context)) + + return AlertDialog.Builder(requireContext()) + .setView(binding.root) + .create() + } + + @SuppressLint("DefaultLocale") + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + + // accountManager = (activity as BaseActivity).userAccountManager + // + // currentStatus?.let { + // binding.emoji.setText(it.icon) + // binding.customStatusInput.text?.clear() + // binding.customStatusInput.setText(it.message) + // visualizeStatus(it.status) + // + // if (it.clearAt > 0) { + // binding.clearStatusAfterSpinner.visibility = View.GONE + // binding.remainingClearTime.apply { + // binding.clearStatusMessageTextView.text = getString(R.string.clear_status_message) + // visibility = View.VISIBLE + // text = DisplayUtils.getRelativeTimestamp(context, it.clearAt * ONE_SECOND_IN_MILLIS, true) + // .toString() + // .decapitalize(Locale.getDefault()) + // setOnClickListener { + // visibility = View.GONE + // binding.clearStatusAfterSpinner.visibility = View.VISIBLE + // binding.clearStatusMessageTextView.text = getString(R.string.clear_status_message_after) + // } + // } + // } + // } + // + // adapter = PredefinedStatusListAdapter(this, requireContext()) + // if (this::predefinedStatus.isInitialized) { + // adapter.list = predefinedStatus + // } + // binding.predefinedStatusList.adapter = adapter + // binding.predefinedStatusList.layoutManager = LinearLayoutManager(context) + // + // binding.onlineStatus.setOnClickListener { setStatus(StatusType.ONLINE) } + // binding.dndStatus.setOnClickListener { setStatus(StatusType.DND) } + // binding.awayStatus.setOnClickListener { setStatus(StatusType.AWAY) } + // binding.invisibleStatus.setOnClickListener { setStatus(StatusType.INVISIBLE) } + // + // binding.clearStatus.setOnClickListener { clearStatus() } + // binding.setStatus.setOnClickListener { setStatusMessage() } + // binding.emoji.setOnClickListener { openEmojiPopup() } + // + // popup = EmojiPopup.Builder + // .fromRootView(view) + // .setOnEmojiClickListener { _, _ -> + // popup.dismiss() + // binding.emoji.clearFocus() + // val imm: InputMethodManager = context?.getSystemService(Context.INPUT_METHOD_SERVICE) as + // InputMethodManager + // imm.hideSoftInputFromWindow(binding.emoji.windowToken, 0) + // } + // .build(binding.emoji) + // binding.emoji.disableKeyboardInput(popup) + // binding.emoji.forceSingleEmoji() + // + // val adapter = ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item) + // adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) + // adapter.add(getString(R.string.dontClear)) + // adapter.add(getString(R.string.thirtyMinutes)) + // adapter.add(getString(R.string.oneHour)) + // adapter.add(getString(R.string.fourHours)) + // adapter.add(getString(R.string.today)) + // adapter.add(getString(R.string.thisWeek)) + // + // binding.clearStatusAfterSpinner.apply { + // this.adapter = adapter + // onItemSelectedListener = object : OnItemSelectedListener { + // override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, id: Long) { + // setClearStatusAfterValue(position) + // } + // + // override fun onNothingSelected(parent: AdapterView<*>?) { + // // nothing to do + // } + // } + // } + // + // binding.clearStatus.setTextColor(ThemeColorUtils.primaryColor(context, true)) + // ThemeButtonUtils.colorPrimaryButton(binding.setStatus, context) + // ThemeTextInputUtils.colorTextInput( + // binding.customStatusInputContainer, + // binding.customStatusInput, + // ThemeColorUtils.primaryColor(activity) + // ) + } + + // @Suppress("ComplexMethod") + // private fun setClearStatusAfterValue(item: Int) { + // when (item) { + // POS_DONT_CLEAR -> { + // // don't clear + // clearAt = null + // } + // + // POS_HALF_AN_HOUR -> { + // // 30 minutes + // clearAt = System.currentTimeMillis() / ONE_SECOND_IN_MILLIS + THIRTY_MINUTES * ONE_MINUTE_IN_SECONDS + // } + // + // POS_AN_HOUR -> { + // // one hour + // clearAt = + // System.currentTimeMillis() / ONE_SECOND_IN_MILLIS + ONE_MINUTE_IN_SECONDS * ONE_MINUTE_IN_SECONDS + // } + // + // POS_FOUR_HOURS -> { + // // four hours + // clearAt = + // System.currentTimeMillis() / ONE_SECOND_IN_MILLIS + // +FOUR_HOURS * ONE_MINUTE_IN_SECONDS * ONE_MINUTE_IN_SECONDS + // } + // + // POS_TODAY -> { + // // today + // val date = Calendar.getInstance().apply { + // set(Calendar.HOUR_OF_DAY, LAST_HOUR_OF_DAY) + // set(Calendar.MINUTE, LAST_MINUTE_OF_HOUR) + // set(Calendar.SECOND, LAST_SECOND_OF_MINUTE) + // } + // clearAt = date.timeInMillis / ONE_SECOND_IN_MILLIS + // } + // + // POS_END_OF_WEEK -> { + // // end of week + // val date = Calendar.getInstance().apply { + // set(Calendar.HOUR_OF_DAY, LAST_HOUR_OF_DAY) + // set(Calendar.MINUTE, LAST_MINUTE_OF_HOUR) + // set(Calendar.SECOND, LAST_SECOND_OF_MINUTE) + // } + // + // while (date.get(Calendar.DAY_OF_WEEK) != Calendar.SUNDAY) { + // date.add(Calendar.DAY_OF_YEAR, 1) + // } + // + // clearAt = date.timeInMillis / ONE_SECOND_IN_MILLIS + // } + // } + // } + // + // @Suppress("ReturnCount") + // private fun clearAtToUnixTime(clearAt: ClearAt?): Long { + // if (clearAt != null) { + // if (clearAt.type.equals("period")) { + // return System.currentTimeMillis() / ONE_SECOND_IN_MILLIS + clearAt.time.toLong() + // } else if (clearAt.type.equals("end-of")) { + // if (clearAt.time.equals("day")) { + // val date = Calendar.getInstance().apply { + // set(Calendar.HOUR_OF_DAY, LAST_HOUR_OF_DAY) + // set(Calendar.MINUTE, LAST_MINUTE_OF_HOUR) + // set(Calendar.SECOND, LAST_SECOND_OF_MINUTE) + // } + // return date.timeInMillis / ONE_SECOND_IN_MILLIS + // } + // } + // } + // + // return -1 + // } + // + // private fun openEmojiPopup() { + // popup.show() + // } + // + // private fun clearStatus() { + // asyncRunner.postQuickTask( + // ClearStatusTask(accountManager.currentOwnCloudAccount?.savedAccount, context), + // { dismiss(it) } + // ) + // } + // + // private fun setStatus(statusType: StatusType) { + // visualizeStatus(statusType) + // + // asyncRunner.postQuickTask( + // SetStatusTask( + // statusType, + // accountManager.currentOwnCloudAccount?.savedAccount, + // context + // ), + // { + // if (!it) { + // clearTopStatus() + // } + // }, + // { clearTopStatus() } + // ) + // } + // + // private fun visualizeStatus(statusType: StatusType) { + // when (statusType) { + // StatusType.ONLINE -> { + // clearTopStatus() + // binding.onlineStatus.setBackgroundColor(ThemeColorUtils.primaryColor(context)) + // } + // StatusType.AWAY -> { + // clearTopStatus() + // binding.awayStatus.setBackgroundColor(ThemeColorUtils.primaryColor(context)) + // } + // StatusType.DND -> { + // clearTopStatus() + // binding.dndStatus.setBackgroundColor(ThemeColorUtils.primaryColor(context)) + // } + // StatusType.INVISIBLE -> { + // clearTopStatus() + // binding.invisibleStatus.setBackgroundColor(ThemeColorUtils.primaryColor(context)) + // } + // else -> clearTopStatus() + // } + // } + // + // private fun clearTopStatus() { + // context?.let { + // val grey = it.resources.getColor(R.color.grey_200) + // binding.onlineStatus.setBackgroundColor(grey) + // binding.awayStatus.setBackgroundColor(grey) + // binding.dndStatus.setBackgroundColor(grey) + // binding.invisibleStatus.setBackgroundColor(grey) + // } + // } + // + // private fun setStatusMessage() { + // if (selectedPredefinedMessageId != null) { + // asyncRunner.postQuickTask( + // SetPredefinedCustomStatusTask( + // selectedPredefinedMessageId!!, + // clearAt, + // accountManager.currentOwnCloudAccount?.savedAccount, + // context + // ), + // { dismiss(it) } + // ) + // } else { + // asyncRunner.postQuickTask( + // SetUserDefinedCustomStatusTask( + // binding.customStatusInput.text.toString(), + // binding.emoji.text.toString(), + // clearAt, + // accountManager.currentOwnCloudAccount?.savedAccount, + // context + // ), + // { dismiss(it) } + // ) + // } + // } + + private fun dismiss(boolean: Boolean) { + if (boolean) { + dismiss() + } + } + + /** + * Fragment creator + */ + companion object { + @JvmStatic + fun newInstance(user: User): SetStatusDialogFragment { + val args = Bundle() + args.putParcelable(ARG_CURRENT_USER_PARAM, user) + //args.putParcelable(ARG_CURRENT_STATUS_PARAM, status) + + val dialogFragment = SetStatusDialogFragment() + dialogFragment.arguments = args + // dialogFragment.setStyle(STYLE_NORMAL, R.style.Theme_ownCloud_Dialog) + return dialogFragment + } + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + return binding.root + } + + // override fun onClick(predefinedStatus: PredefinedStatus) { + // selectedPredefinedMessageId = predefinedStatus.id + // clearAt = clearAtToUnixTime(predefinedStatus.clearAt) + // binding.emoji.setText(predefinedStatus.icon) + // binding.customStatusInput.text?.clear() + // binding.customStatusInput.text?.append(predefinedStatus.message) + // + // binding.remainingClearTime.visibility = View.GONE + // binding.clearStatusAfterSpinner.visibility = View.VISIBLE + // binding.clearStatusMessageTextView.text = getString(R.string.clear_status_message_after) + // + // if (predefinedStatus.clearAt == null) { + // binding.clearStatusAfterSpinner.setSelection(0) + // } else { + // val clearAt = predefinedStatus.clearAt!! + // if (clearAt.type.equals("period")) { + // when (clearAt.time) { + // "1800" -> binding.clearStatusAfterSpinner.setSelection(POS_HALF_AN_HOUR) + // "3600" -> binding.clearStatusAfterSpinner.setSelection(POS_AN_HOUR) + // "14400" -> binding.clearStatusAfterSpinner.setSelection(POS_FOUR_HOURS) + // else -> binding.clearStatusAfterSpinner.setSelection(POS_DONT_CLEAR) + // } + // } else if (clearAt.type.equals("end-of")) { + // when (clearAt.time) { + // "day" -> binding.clearStatusAfterSpinner.setSelection(POS_TODAY) + // "week" -> binding.clearStatusAfterSpinner.setSelection(POS_END_OF_WEEK) + // else -> binding.clearStatusAfterSpinner.setSelection(POS_DONT_CLEAR) + // } + // } + // } + // setClearStatusAfterValue(binding.clearStatusAfterSpinner.selectedItemPosition) + // } + // + // @VisibleForTesting + // fun setPredefinedStatus(predefinedStatus: ArrayList) { + // adapter.list = predefinedStatus + // binding.predefinedStatusList.adapter?.notifyDataSetChanged() + // } +} diff --git a/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java b/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java index 01c5c7f74..94ed1ac0d 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java +++ b/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java @@ -416,4 +416,12 @@ public class ApiUtils { public static String getUrlForSetChatReadMarker(int version, String baseUrl, String roomToken) { return getUrlForChat(version, baseUrl, roomToken) + "/read"; } + + /* + * OCS Status API + */ + + public static String getUrlForStatus(String baseUrl) { + return baseUrl + ocsApiVersion + "/apps/user_status/api/v1/user_status"; + } } diff --git a/app/src/main/res/drawable/ic_edit.xml b/app/src/main/res/drawable/ic_edit.xml new file mode 100644 index 000000000..406f0b5f6 --- /dev/null +++ b/app/src/main/res/drawable/ic_edit.xml @@ -0,0 +1,34 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_user_status_away.xml b/app/src/main/res/drawable/ic_user_status_away.xml new file mode 100644 index 000000000..ab5ca9642 --- /dev/null +++ b/app/src/main/res/drawable/ic_user_status_away.xml @@ -0,0 +1,32 @@ + + + + diff --git a/app/src/main/res/drawable/ic_user_status_dnd.xml b/app/src/main/res/drawable/ic_user_status_dnd.xml new file mode 100644 index 000000000..27cfc1066 --- /dev/null +++ b/app/src/main/res/drawable/ic_user_status_dnd.xml @@ -0,0 +1,38 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_user_status_invisible.xml b/app/src/main/res/drawable/ic_user_status_invisible.xml new file mode 100644 index 000000000..18a35e8e1 --- /dev/null +++ b/app/src/main/res/drawable/ic_user_status_invisible.xml @@ -0,0 +1,34 @@ + + + + + diff --git a/app/src/main/res/drawable/online_status.xml b/app/src/main/res/drawable/online_status.xml new file mode 100644 index 000000000..fb042c2d3 --- /dev/null +++ b/app/src/main/res/drawable/online_status.xml @@ -0,0 +1,23 @@ + + + + diff --git a/app/src/main/res/layout/dialog_choose_account.xml b/app/src/main/res/layout/dialog_choose_account.xml index b15e29920..702a7392a 100644 --- a/app/src/main/res/layout/dialog_choose_account.xml +++ b/app/src/main/res/layout/dialog_choose_account.xml @@ -17,6 +17,7 @@ --> @@ -31,6 +32,42 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> + + + + + + + + app:layout_constraintTop_toBottomOf="@id/statusView" /> . +--> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/predefined_status.xml b/app/src/main/res/layout/predefined_status.xml new file mode 100644 index 000000000..69e080c4f --- /dev/null +++ b/app/src/main/res/layout/predefined_status.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/values-night/colors.xml index 622e1a854..6b62f1190 100644 --- a/app/src/main/res/values-night/colors.xml +++ b/app/src/main/res/values-night/colors.xml @@ -65,4 +65,6 @@ #4B4B4B #282828 + + #222222 diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 8b059a897..a152cde85 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -97,4 +97,11 @@ #99121212 + #eeeeee + #818181 + #D6D7D7 + #000000 + #007cc2 + #ffffff + diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 2bcef5826..753129d95 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -63,4 +63,10 @@ 180dp 110dp 0dp + + 52dp + 4dp + 16sp + 48dp + 2dp diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 345a31a82..05c9d63d2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -268,6 +268,27 @@ Remove group and members Pin: %1$s + + Set status + Online status + Status message + What is your status? + Clear status message after + Clear status message + Set status message + Online + Do not disturb + Away + Invisible + + 😃 + Don\'t clear + Today + 30 minutes + 1 hour + 4 hours + This week + Unread mentions Conversations diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 89d41e79b..d97926026 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -240,4 +240,12 @@ adjustResize + +