diff options
author | Marcel Hibbe <dev@mhibbe.de> | 2021-12-28 17:10:29 +0300 |
---|---|---|
committer | Marcel Hibbe <dev@mhibbe.de> | 2022-02-25 14:11:36 +0300 |
commit | 05586ccf47c8bfc24499fd6c06fe87543200a3f9 (patch) | |
tree | 5a359248b20b9534615d953628a25525ee8f2b0c | |
parent | 3f6ef9aed226d8c2a5085fe3a93afcd549e91947 (diff) |
add user status option to account dialog (WIP)
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
23 files changed, 1780 insertions, 31 deletions
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<RoomsOverall> getOpenConversations(@Header("Authorization") String authorization, @Url String url); + + /* + * OCS Status API + */ + @GET + Observable<StatusOverall> 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 <mario@lovelyhq.com> + * + * 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 <http://www.gnu.org/licenses/>. + */ + +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<String, List<String>> 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<String, List<String>> 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<String, List<String>> 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 <t@timkrueger.me> + * + * 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 <http://www.gnu.org/licenses/>. + */ +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 <t@timkrueger.me> + * + * 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 <http://www.gnu.org/licenses/>. + */ +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 <t@timkrueger.me> + * + * 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 <http://www.gnu.org/licenses/>. + */ +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<StatusOverall>() { + + 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<UserEntity>() { @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 <http://www.gnu.org/licenses/>. + */ + +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<PredefinedStatus> + // 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<ArrayList<PredefinedStatus>>() {}.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<String>(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<PredefinedStatus>) { + // 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 @@ +<!-- + ~ + ~ Nextcloud Android client application + ~ + ~ @author Tobias Kaminsky + ~ Copyright (C) 2019 Tobias Kaminsky + ~ Copyright (C) 2019 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 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 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 <https://www.gnu.org/licenses/>. + --> + +<vector xmlns:tools="http://schemas.android.com/tools" + android:autoMirrored="true" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24" + android:width="24dp" + xmlns:android="http://schemas.android.com/apk/res/android" + tools:ignore="VectorRaster"> + <path + android:fillColor="#FF000000" + android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z" /> +</vector> 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 @@ +<!-- + Nextcloud Android client application + + @author Tobias Kaminsky + Copyright (C) 2020 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 <http://www.gnu.org/licenses/>. +--> +<vector xmlns:tools="http://schemas.android.com/tools" + android:autoMirrored="true" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24" + android:width="24dp" + xmlns:android="http://schemas.android.com/apk/res/android" + tools:ignore="VectorRaster"> + <path + android:fillColor="#f4a331" + android:pathData="m10.615,2.1094c-4.8491,0.6811 -8.6152,4.8615 -8.6152,9.8906 0,5.5 4.5,10 10,10 5.0292,0 9.2096,-3.7661 9.8906,-8.6152 -1.4654,1.601 -3.5625,2.6152 -5.8906,2.6152 -4.4,0 -8,-3.6 -8,-8 0,-2.3281 1.0143,-4.4252 2.6152,-5.8906z" /> +</vector> 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 @@ +<!-- + Nextcloud Android client application + + @author Tobias Kaminsky + Copyright (C) 2020 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 <http://www.gnu.org/licenses/>. +--> +<vector xmlns:tools="http://schemas.android.com/tools" + android:autoMirrored="true" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24" + android:width="24dp" + xmlns:android="http://schemas.android.com/apk/res/android" + tools:ignore="VectorRaster"> + <path + android:fillColor="#ed484c" + android:pathData="m12,2c-5.52,0 -10,4.48 -10,10s4.48,10 10,10 10,-4.48 10,-10 -4.48,-10 -10,-10z" /> + <path + android:fillColor="#fdffff" + android:pathData="m8,10h8c1.108,0 2,0.892 2,2s-0.892,2 -2,2h-8c-1.108,0 -2,-0.892 -2,-2s0.892,-2 2,-2z" + android:strokeLineCap="round" + android:strokeLineJoin="round" + android:strokeWidth="2" /> +</vector> 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 @@ +<!-- + ~ + ~ Nextcloud Android client application + ~ + ~ @author Tobias Kaminsky + ~ Copyright (C) 2020 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 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 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 <https://www.gnu.org/licenses/>. + --> + +<vector xmlns:tools="http://schemas.android.com/tools" + android:autoMirrored="true" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24" + android:width="24dp" + xmlns:android="http://schemas.android.com/apk/res/android" + tools:ignore="VectorRaster"> + <path + android:fillColor="#000000" + android:pathData="m12,2c-5.52,0 -10,4.48 -10,10s4.48,10 10,10 10,-4.48 10,-10 -4.48,-10 -10,-10zM12,6a6,6 0,0 1,6 6,6 6,0 0,1 -6,6 6,6 0,0 1,-6 -6,6 6,0 0,1 6,-6z" /> +</vector> 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 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + Nextcloud Android client application + + @author Andy Scherzinger + Copyright (C) 2019 Andy Scherzinger + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero 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 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 <http://www.gnu.org/licenses/>. +--> +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="oval"> + <solid android:color="#00ff00" /> +</shape> 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 @@ --> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> @@ -31,6 +32,42 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> + <LinearLayout + android:id="@+id/statusView" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:visibility="gone" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/current_account" + tools:visibility="visible"> + + <View + android:layout_width="match_parent" + android:layout_height="1dp" + android:layout_marginTop="4dp" + android:background="@color/list_divider_background" /> + + <com.google.android.material.button.MaterialButton + android:id="@+id/set_status" + style="@style/Nextcloud.Material.TextButton" + android:layout_width="match_parent" + android:layout_height="50dp" + android:layout_marginStart="12dp" + android:layout_marginEnd="12dp" + android:paddingStart="10dp" + android:paddingEnd="0dp" + android:text="@string/set_status" + android:textAlignment="textStart" + android:textAllCaps="false" + android:textColor="@color/fontAppbar" + app:icon="@drawable/ic_edit" + app:iconGravity="start" + app:iconPadding="22dp" + app:iconTint="@color/fontAppbar" /> + </LinearLayout> + <View android:id="@+id/separator_line" android:layout_width="0dp" @@ -39,7 +76,7 @@ android:background="@color/controller_chat_separator" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/current_account" /> + app:layout_constraintTop_toBottomOf="@id/statusView" /> <androidx.recyclerview.widget.RecyclerView android:id="@+id/accounts_list" diff --git a/app/src/main/res/layout/dialog_set_status.xml b/app/src/main/res/layout/dialog_set_status.xml new file mode 100644 index 000000000..521f6db42 --- /dev/null +++ b/app/src/main/res/layout/dialog_set_status.xml @@ -0,0 +1,453 @@ +<!-- + Nextcloud Android client application + + Copyright (C) 2020 Andy Scherzinger + Copyright (C) 2020 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 General Public License version 2, + as published by the Free Software Foundation. + + 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 <http://www.gnu.org/licenses/>. +--> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:padding="@dimen/standard_padding"> + + <TextView + android:id="@+id/onlineStatusView" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="@dimen/standard_half_margin" + android:text="@string/online_status" + android:textColor="@color/high_emphasis_text" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + <LinearLayout + android:id="@+id/statusView" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:baselineAligned="false" + android:orientation="vertical" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/onlineStatusView"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="@dimen/standard_margin" + android:orientation="horizontal"> + + <com.google.android.material.card.MaterialCardView + android:id="@+id/onlineStatus" + android:layout_width="match_parent" + android:layout_height="@dimen/online_status_item_height" + android:layout_gravity="center_vertical" + android:layout_marginEnd="@dimen/standard_half_margin" + android:layout_weight="1" + android:orientation="horizontal" + app:cardBackgroundColor="@color/grey_200" + app:cardElevation="0dp"> + + <RelativeLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:layout_marginStart="@dimen/standard_margin" + android:layout_marginEnd="@dimen/standard_margin" + android:orientation="horizontal" + tools:ignore="UnusedAttribute"> + + <ImageView + android:id="@+id/online_icon" + android:layout_width="@dimen/iconized_single_line_item_icon_size" + android:layout_height="@dimen/iconized_single_line_item_icon_size" + android:layout_alignParentStart="true" + android:layout_centerVertical="true" + android:layout_gravity="top|start" + android:layout_marginEnd="@dimen/standard_half_margin" + android:contentDescription="@null" + android:src="@drawable/online_status" + app:tint="@color/hwSecurityGreen" /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_centerInParent="true" + android:layout_toEndOf="@id/online_icon" + android:orientation="vertical"> + + <TextView + android:id="@+id/online_headline" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:ellipsize="end" + android:gravity="bottom" + android:maxLines="1" + android:text="@string/online" + android:textAppearance="?android:attr/textAppearanceListItem" /> + + <TextView + android:id="@+id/online_text" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/standard_half_margin" + android:layout_marginBottom="@dimen/standard_quarter_margin" + android:ellipsize="end" + android:gravity="top" + android:maxLines="1" + android:textColor="?android:attr/textColorSecondary" + android:visibility="gone" /> + + </LinearLayout> + + </RelativeLayout> + + </com.google.android.material.card.MaterialCardView> + + <com.google.android.material.card.MaterialCardView + android:id="@+id/awayStatus" + android:layout_width="match_parent" + android:layout_height="@dimen/online_status_item_height" + android:layout_gravity="center_vertical" + android:layout_marginStart="@dimen/standard_half_margin" + android:layout_weight="1" + android:orientation="horizontal" + app:cardBackgroundColor="@color/grey_200" + app:cardElevation="0dp"> + + <RelativeLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:layout_marginStart="@dimen/standard_margin" + android:layout_marginEnd="@dimen/standard_margin" + android:orientation="horizontal" + tools:ignore="UnusedAttribute"> + + <ImageView + android:id="@+id/away_icon" + android:layout_width="@dimen/iconized_single_line_item_icon_size" + android:layout_height="@dimen/iconized_single_line_item_icon_size" + android:layout_alignParentStart="true" + android:layout_centerVertical="true" + android:layout_gravity="top|start" + android:layout_marginEnd="@dimen/standard_half_margin" + android:contentDescription="@null" + android:src="@drawable/ic_user_status_away" + app:tint="#f4a331" /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_centerInParent="true" + android:layout_toEndOf="@id/away_icon" + android:orientation="vertical"> + + <TextView + android:id="@+id/away_headline" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:ellipsize="end" + android:gravity="bottom" + android:maxLines="1" + android:text="@string/away" + android:textAppearance="?android:attr/textAppearanceListItem" /> + + <TextView + android:id="@+id/away_text" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/standard_half_margin" + android:layout_marginBottom="@dimen/standard_quarter_margin" + android:ellipsize="end" + android:gravity="top" + android:maxLines="1" + android:textColor="?android:attr/textColorSecondary" + android:visibility="gone" /> + + </LinearLayout> + + </RelativeLayout> + + </com.google.android.material.card.MaterialCardView> + + + </LinearLayout> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="@dimen/standard_margin" + android:orientation="horizontal"> + + <com.google.android.material.card.MaterialCardView + android:id="@+id/dndStatus" + android:layout_width="match_parent" + android:layout_height="@dimen/online_status_item_height" + android:layout_gravity="center_vertical" + android:layout_marginEnd="@dimen/standard_half_margin" + android:layout_weight="1" + android:orientation="horizontal" + app:cardBackgroundColor="@color/grey_200" + app:cardElevation="0dp"> + + <RelativeLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:layout_marginStart="@dimen/standard_margin" + android:layout_marginEnd="@dimen/standard_margin" + android:orientation="horizontal" + tools:ignore="UnusedAttribute"> + + <ImageView + android:id="@+id/dnd_icon" + android:layout_width="@dimen/iconized_single_line_item_icon_size" + android:layout_height="@dimen/iconized_single_line_item_icon_size" + android:layout_alignParentStart="true" + android:layout_centerVertical="true" + android:layout_gravity="top|start" + android:layout_marginEnd="@dimen/standard_half_margin" + android:contentDescription="@null" + android:src="@drawable/ic_user_status_dnd" /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_centerInParent="true" + android:layout_toEndOf="@id/dnd_icon" + android:orientation="vertical"> + + <TextView + android:id="@+id/dnd_headline" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:ellipsize="end" + android:gravity="bottom" + android:maxLines="1" + android:text="@string/dnd" + android:textAppearance="?android:attr/textAppearanceListItem" /> + + </LinearLayout> + + </RelativeLayout> + + </com.google.android.material.card.MaterialCardView> + + <com.google.android.material.card.MaterialCardView + android:id="@+id/invisibleStatus" + android:layout_width="match_parent" + android:layout_height="@dimen/online_status_item_height" + android:layout_gravity="center_vertical" + android:layout_marginStart="@dimen/standard_half_margin" + + android:layout_weight="1" + android:orientation="horizontal" + app:cardBackgroundColor="@color/grey_200" + app:cardElevation="0dp"> + + <RelativeLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:layout_marginStart="@dimen/standard_margin" + android:layout_marginEnd="@dimen/standard_margin" + android:orientation="horizontal" + tools:ignore="UnusedAttribute"> + + <ImageView + android:id="@+id/invisible_icon" + android:layout_width="@dimen/iconized_single_line_item_icon_size" + android:layout_height="@dimen/iconized_single_line_item_icon_size" + android:layout_alignParentStart="true" + android:layout_centerVertical="true" + android:layout_gravity="top|start" + android:layout_marginEnd="@dimen/standard_half_margin" + android:contentDescription="@null" + android:src="@drawable/ic_user_status_invisible" /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_centerInParent="true" + android:layout_toEndOf="@id/invisible_icon" + android:orientation="vertical"> + + <TextView + android:id="@+id/invisible_headline" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:ellipsize="end" + android:gravity="bottom" + android:maxLines="1" + android:text="@string/invisible" + android:textAppearance="?android:attr/textAppearanceListItem" /> + + </LinearLayout> + + </RelativeLayout> + + </com.google.android.material.card.MaterialCardView> + + </LinearLayout> + </LinearLayout> + + + <View + android:id="@+id/separator_line" + android:layout_width="0dp" + android:layout_height="1dp" + android:layout_marginTop="@dimen/standard_quarter_margin" + android:background="@color/list_divider_background" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/statusView" /> + + <LinearLayout + android:id="@+id/fragment_container" + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1" + android:orientation="vertical"> + + <TextView + android:id="@+id/statusMessage" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="@dimen/standard_half_margin" + android:text="@string/status_message" + android:textColor="@color/high_emphasis_text" + android:textSize="@dimen/activity_list_item_title_header_text_size" + app:layout_constraintTop_toBottomOf="@+id/statusView" /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + + <com.google.android.material.card.MaterialCardView + android:id="@+id/emojiCard" + android:layout_width="@dimen/activity_row_layout_height" + android:layout_height="@dimen/activity_row_layout_height" + android:layout_gravity="center" + android:layout_marginTop="@dimen/standard_eighth_margin" + android:layout_marginEnd="@dimen/standard_margin" + android:orientation="horizontal" + app:cardBackgroundColor="@color/grey_200" + app:cardCornerRadius="24dp" + app:cardElevation="0dp"> + + <com.vanniktech.emoji.EmojiEditText + android:id="@+id/emoji" + android:layout_width="@dimen/activity_row_layout_height" + android:layout_height="@dimen/activity_row_layout_height" + android:background="@color/grey_200" + android:cursorVisible="false" + android:gravity="center" + android:text="@string/default_emoji" + android:textSize="24sp" /> + + </com.google.android.material.card.MaterialCardView> + + <com.google.android.material.textfield.TextInputLayout + android:id="@+id/customStatusInput_container" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:hint="@string/whats_your_status"> + + <com.google.android.material.textfield.TextInputEditText + android:id="@+id/customStatusInput" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:ems="10" + android:importantForAutofill="no" + android:inputType="textAutoCorrect" + android:scrollbars="vertical"> + + </com.google.android.material.textfield.TextInputEditText> + + </com.google.android.material.textfield.TextInputLayout> + + </LinearLayout> + + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/predefinedStatusList" + android:layout_width="match_parent" + android:layout_height="wrap_content" + tools:itemCount="5" + tools:listitem="@layout/predefined_status" /> + + </LinearLayout> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/standard_half_margin" + android:orientation="horizontal"> + + <TextView + android:id="@+id/clearStatusMessageTextView" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/clear_status_message_after" + android:textColor="@color/high_emphasis_text" /> + + <Spinner + android:id="@+id/clearStatusAfterSpinner" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + + <TextView + android:id="@+id/remainingClearTime" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textColor="@color/high_emphasis_text" + android:layout_marginStart="4dp" + android:visibility="gone" /> + + </LinearLayout> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/standard_half_margin" + android:orientation="horizontal"> + + <com.google.android.material.button.MaterialButton + android:id="@+id/clearStatus" + style="@style/OutlinedButton" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginEnd="@dimen/standard_half_margin" + android:layout_weight="1" + android:text="@string/clear_status_message" + app:cornerRadius="@dimen/button_corner_radius" /> + + <com.google.android.material.button.MaterialButton + android:id="@+id/setStatus" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="1" + android:text="@string/set_status_message" + android:theme="@style/Button.Primary" + app:cornerRadius="@dimen/button_corner_radius" /> + + </LinearLayout> + +</LinearLayout> 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 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + + Nextcloud Android client application + + Copyright (C) 2020 Andy Scherzinger + Copyright (C) 2020 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 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 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 <https://www.gnu.org/licenses/>. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="48dp"> + + <TextView + android:id="@+id/icon" + android:layout_width="48dp" + android:layout_height="match_parent" + android:gravity="center" + android:textSize="25sp" + tools:text="📆" /> + + <TextView + android:id="@+id/name" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:gravity="center_vertical" + android:textAppearance="?android:attr/textAppearanceListItem" + tools:text="In a meeting" /> + + <TextView + android:id="@+id/divider" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_margin="@dimen/standard_half_margin" + android:gravity="center_vertical" + android:text="@string/divider" + android:textAppearance="?android:attr/textAppearanceListItem" + android:textColor="?android:attr/textColorSecondary" /> + + <TextView + android:id="@+id/clearAt" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:gravity="center_vertical" + android:textAppearance="?android:attr/textAppearanceListItem" + android:textColor="?android:attr/textColorSecondary" + tools:text="an hour" /> +</LinearLayout> 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 @@ <!-- shimmer element colors --> <color name="nc_shimmer_default_color">#4B4B4B</color> <color name="nc_shimmer_darker_color">#282828</color> + + <color name="list_divider_background">#222222</color> </resources> 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 @@ <color name="camera_bg_tint">#99121212</color> + <color name="list_divider_background">#eeeeee</color> + <color name="grey_200">#818181</color> + <color name="secondary_button_background_color">#D6D7D7</color> + <color name="secondary_button_text_color">#000000</color> + <color name="primary_button_background_color">#007cc2</color> + <color name="primary_button_text_color">#ffffff</color> + </resources> 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 @@ <dimen name="call_grid_item_min_height">180dp</dimen> <dimen name="call_controls_height">110dp</dimen> <dimen name="zero">0dp</dimen> + + <dimen name="online_status_item_height">52dp</dimen> + <dimen name="standard_quarter_margin">4dp</dimen> + <dimen name="activity_list_item_title_header_text_size">16sp</dimen> + <dimen name="activity_row_layout_height">48dp</dimen> + <dimen name="standard_eighth_margin">2dp</dimen> </resources> 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 @@ <string name="nc_remove_group_and_members">Remove group and members</string> <string name="nc_attendee_pin">Pin: %1$s</string> + <!-- User Status --> + <string name="set_status">Set status</string> + <string name="online_status">Online status</string> + <string name="status_message">Status message</string> + <string name="whats_your_status">What is your status?</string> + <string name="clear_status_message_after">Clear status message after</string> + <string name="clear_status_message">Clear status message</string> + <string name="set_status_message">Set status message</string> + <string name="online">Online</string> + <string name="dnd">Do not disturb</string> + <string name="away">Away</string> + <string name="invisible">Invisible</string> + <string translatable="false" name="divider">—</string> + <string translatable="false" name="default_emoji">😃</string> + <string name="dontClear">Don\'t clear</string> + <string name="today">Today</string> + <string name="thirtyMinutes">30 minutes</string> + <string name="oneHour">1 hour</string> + <string name="fourHours">4 hours</string> + <string name="thisWeek">This week</string> + <!-- Conversations List--> <string name="nc_new_mention">Unread mentions</string> <string name="conversations">Conversations</string> 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 @@ <item name="android:windowSoftInputMode">adjustResize</item> </style> + <style name="OutlinedButton" parent="Widget.MaterialComponents.Button.OutlinedButton"> + <item name="colorAccent">@color/transparent</item> + <item name="android:textColor">@color/colorPrimaryDark</item> + <item name="android:textAllCaps">false</item> + <item name="android:typeface">sans</item> + <item name="android:textStyle">bold</item> + </style> + </resources> |