add user status option to account dialog (WIP)

Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
This commit is contained in:
Marcel Hibbe 2021-12-28 15:10:29 +01:00
parent 3f6ef9aed2
commit 05586ccf47
No known key found for this signature in database
GPG Key ID: C793F8B59F43CE7B
23 changed files with 1781 additions and 32 deletions

View File

@ -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);
}

View File

@ -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 {

View File

@ -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() + ")";
}
}

View File

@ -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;
}
}

View File

@ -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 +
'}';
}
}

View File

@ -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 +
'}';
}
}

View File

@ -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 +
'}';
}
}

View File

@ -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;
}
};
}

View File

@ -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()
// }
}

View File

@ -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";
}
}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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"

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>