From a94f0f1bf12126f31f293464032d70a48973d190 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Kr=C3=BCger?= Date: Wed, 12 Jan 2022 17:33:37 +0100 Subject: [PATCH] Adopt more parts from files app [WIP] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tim Krüger --- app/build.gradle | 4 +- .../adapters/PredefinedStatusClickListener.kt | 29 + .../adapters/PredefinedStatusListAdapter.kt | 50 ++ .../adapters/PredefinedStatusViewHolder.kt | 59 ++ .../java/com/nextcloud/talk/api/NcApi.java | 5 + .../application/NextcloudTalkApplication.kt | 4 +- .../talk/models/json/status/ClearAt.kt | 4 + .../models/json/status/PredefinedStatus.kt | 3 + .../talk/models/json/status/Status.java | 159 ----- .../talk/models/json/status/Status.kt | 52 ++ .../talk/models/json/status/StatusType.kt | 11 + .../dialog/ChooseAccountDialogFragment.java | 25 +- .../talk/ui/dialog/SetStatusDialogFragment.kt | 628 ++++++++++-------- .../com/nextcloud/talk/utils/ApiUtils.java | 4 + .../nextcloud/talk/utils/DisplayUtils.java | 67 ++ app/src/main/res/values/strings.xml | 1 + 16 files changed, 647 insertions(+), 458 deletions(-) create mode 100644 app/src/main/java/com/nextcloud/talk/adapters/PredefinedStatusClickListener.kt create mode 100644 app/src/main/java/com/nextcloud/talk/adapters/PredefinedStatusListAdapter.kt create mode 100644 app/src/main/java/com/nextcloud/talk/adapters/PredefinedStatusViewHolder.kt create mode 100644 app/src/main/java/com/nextcloud/talk/models/json/status/ClearAt.kt create mode 100644 app/src/main/java/com/nextcloud/talk/models/json/status/PredefinedStatus.kt delete mode 100644 app/src/main/java/com/nextcloud/talk/models/json/status/Status.java create mode 100644 app/src/main/java/com/nextcloud/talk/models/json/status/Status.kt create mode 100644 app/src/main/java/com/nextcloud/talk/models/json/status/StatusType.kt diff --git a/app/build.gradle b/app/build.gradle index 6b894348c..20d0d23c3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -194,7 +194,9 @@ dependencies { implementation 'androidx.appcompat:appcompat:1.3.1' implementation 'com.google.android.material:material:1.4.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.3' - implementation 'com.github.vanniktech:Emoji:0.6.0' // 0.7.0 has display issue - don't update to 0.7.0 + // implementation 'com.github.vanniktech:Emoji:0.6.0' // 0.7.0 has display issue - don't update to 0.7.0 + implementation "com.vanniktech:emoji-google:0.8.0" + // implementation "com.vanniktech:emoji-google-compat:0.8.0" implementation group: 'androidx.emoji', name: 'emoji-bundled', version: '1.1.0' implementation 'org.michaelevans.colorart:library:0.0.3' implementation "androidx.work:work-runtime:${workVersion}" diff --git a/app/src/main/java/com/nextcloud/talk/adapters/PredefinedStatusClickListener.kt b/app/src/main/java/com/nextcloud/talk/adapters/PredefinedStatusClickListener.kt new file mode 100644 index 000000000..fa2485dcd --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/adapters/PredefinedStatusClickListener.kt @@ -0,0 +1,29 @@ +/* + * + * 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 . + */ + +package com.nextcloud.talk.adapters + +import com.nextcloud.talk.models.json.status.PredefinedStatus + +interface PredefinedStatusClickListener { + fun onClick(predefinedStatus: PredefinedStatus) +} diff --git a/app/src/main/java/com/nextcloud/talk/adapters/PredefinedStatusListAdapter.kt b/app/src/main/java/com/nextcloud/talk/adapters/PredefinedStatusListAdapter.kt new file mode 100644 index 000000000..253304af6 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/adapters/PredefinedStatusListAdapter.kt @@ -0,0 +1,50 @@ +/* + * + * 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 . + */ + +package com.nextcloud.talk.adapters + +import android.content.Context +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.nextcloud.talk.databinding.PredefinedStatusBinding +import com.nextcloud.talk.models.json.status.PredefinedStatus + +class PredefinedStatusListAdapter( + private val clickListener: PredefinedStatusClickListener, + val context: Context +) : RecyclerView.Adapter() { + internal var list: List = emptyList() + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PredefinedStatusViewHolder { + val itemBinding = PredefinedStatusBinding.inflate(LayoutInflater.from(parent.context), parent, false) + return PredefinedStatusViewHolder(itemBinding) + } + + override fun onBindViewHolder(holder: PredefinedStatusViewHolder, position: Int) { + holder.bind(list[position], clickListener, context) + } + + override fun getItemCount(): Int { + return list.size + } +} diff --git a/app/src/main/java/com/nextcloud/talk/adapters/PredefinedStatusViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/PredefinedStatusViewHolder.kt new file mode 100644 index 000000000..2671896c5 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/adapters/PredefinedStatusViewHolder.kt @@ -0,0 +1,59 @@ +/* + * + * 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 . + */ + +package com.nextcloud.talk.adapters + +import android.content.Context +import androidx.recyclerview.widget.RecyclerView +import com.nextcloud.talk.R +import com.nextcloud.talk.databinding.PredefinedStatusBinding +import com.nextcloud.talk.models.json.status.PredefinedStatus +import com.nextcloud.talk.utils.DisplayUtils + +private const val ONE_SECOND_IN_MILLIS = 1000 + +class PredefinedStatusViewHolder(private val binding: PredefinedStatusBinding) : RecyclerView.ViewHolder(binding.root) { + + fun bind(status: PredefinedStatus, clickListener: PredefinedStatusClickListener, context: Context) { + binding.root.setOnClickListener { clickListener.onClick(status) } + binding.icon.text = status.icon + binding.name.text = status.message + + if (status.clearAt == null) { + binding.clearAt.text = context.getString(R.string.dontClear) + } else { + val clearAt = status.clearAt!! + if (clearAt.type.equals("period")) { + binding.clearAt.text = DisplayUtils.getRelativeTimestamp( + context, + System.currentTimeMillis() + clearAt.time.toInt() * ONE_SECOND_IN_MILLIS, + true + ) + } else { + // end-of + if (clearAt.time.equals("day")) { + binding.clearAt.text = context.getString(R.string.today) + } + } + } + } +} 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 74c17649a..e693731f3 100644 --- a/app/src/main/java/com/nextcloud/talk/api/NcApi.java +++ b/app/src/main/java/com/nextcloud/talk/api/NcApi.java @@ -450,4 +450,9 @@ public interface NcApi { */ @GET Observable status(@Header("Authorization") String authorization, @Url String url); + + @DELETE + Observable statusDeleteMessage(@Header("Authorization") String authorization, @Url String url); + + } diff --git a/app/src/main/java/com/nextcloud/talk/application/NextcloudTalkApplication.kt b/app/src/main/java/com/nextcloud/talk/application/NextcloudTalkApplication.kt index 4c391d3b5..2ae6de462 100644 --- a/app/src/main/java/com/nextcloud/talk/application/NextcloudTalkApplication.kt +++ b/app/src/main/java/com/nextcloud/talk/application/NextcloudTalkApplication.kt @@ -66,7 +66,7 @@ import com.nextcloud.talk.utils.database.user.UserModule import com.nextcloud.talk.utils.preferences.AppPreferences import com.nextcloud.talk.webrtc.MagicWebRTCUtils import com.vanniktech.emoji.EmojiManager -import com.vanniktech.emoji.googlecompat.GoogleCompatEmojiProvider +import com.vanniktech.emoji.google.GoogleEmojiProvider import de.cotech.hw.SecurityKeyManager import de.cotech.hw.SecurityKeyManagerConfig import okhttp3.OkHttpClient @@ -188,7 +188,7 @@ class NextcloudTalkApplication : MultiDexApplication(), LifecycleObserver { config.setReplaceAll(true) val emojiCompat = EmojiCompat.init(config) - EmojiManager.install(GoogleCompatEmojiProvider(emojiCompat)) + EmojiManager.install(GoogleEmojiProvider()) NotificationUtils.registerNotificationChannels(applicationContext, appPreferences) } diff --git a/app/src/main/java/com/nextcloud/talk/models/json/status/ClearAt.kt b/app/src/main/java/com/nextcloud/talk/models/json/status/ClearAt.kt new file mode 100644 index 000000000..aa6107447 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/models/json/status/ClearAt.kt @@ -0,0 +1,4 @@ +package com.nextcloud.talk.models.json.status + + +class ClearAt(val type: String, val time: String) \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/models/json/status/PredefinedStatus.kt b/app/src/main/java/com/nextcloud/talk/models/json/status/PredefinedStatus.kt new file mode 100644 index 000000000..0f689df2f --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/models/json/status/PredefinedStatus.kt @@ -0,0 +1,3 @@ +package com.nextcloud.talk.models.json.status + +class PredefinedStatus(val id: String, val icon: String, val message: String, val clearAt: ClearAt?) \ No newline at end of file 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 deleted file mode 100644 index d6f17d220..000000000 --- a/app/src/main/java/com/nextcloud/talk/models/json/status/Status.java +++ /dev/null @@ -1,159 +0,0 @@ -/* - * - * Nextcloud Talk application - * - * @author Tim Krüger - * Copyright (C) 2021 Tim Krüger - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.nextcloud.talk.models.json.status; - -import com.bluelinelabs.logansquare.annotation.JsonField; -import com.bluelinelabs.logansquare.annotation.JsonObject; - -import org.parceler.Parcel; - -import java.util.Objects; - -@Parcel -@JsonObject -public class Status { - - @JsonField(name = "userId") - public String userId; - - @JsonField(name = "message") - public String message; - - // TODO: Change to enum - @JsonField(name = "messageId") - public String messageId; - - @JsonField(name = "messageIsPredefined") - public boolean messageIsPredefined; - - @JsonField(name = "icon") - public String icon; - - @JsonField(name = "clearAt") - public long clearAt; - - // TODO: Change to enum - @JsonField(name = "status") - public String status; - - @JsonField(name = "statusIsUserDefined") - public boolean statusIsUserDefined; - - public String getMessage() { - return message; - } - - public void setMessage(String message) { - this.message = message; - } - - public String getMessageId() { - return messageId; - } - - public void setMessageId(String messageId) { - this.messageId = messageId; - } - - public boolean isMessageIsPredefined() { - return messageIsPredefined; - } - - public void setMessageIsPredefined(boolean messageIsPredefined) { - this.messageIsPredefined = messageIsPredefined; - } - - public String getIcon() { - return icon; - } - - public void setIcon(String icon) { - this.icon = icon; - } - - public long getClearAt() { - return clearAt; - } - - public void setClearAt(long clearAt) { - this.clearAt = clearAt; - } - - public String getStatus() { - return status; - } - - public void setStatus(String status) { - this.status = status; - } - - public boolean isStatusIsUserDefined() { - return statusIsUserDefined; - } - - public void setStatusIsUserDefined(boolean statusIsUserDefined) { - this.statusIsUserDefined = statusIsUserDefined; - } - - public String getUserId() { - return this.userId; - } - - public void setUserId(String userId) { - this.userId = userId; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - Status status1 = (Status) o; - return messageIsPredefined == status1.messageIsPredefined && - clearAt == status1.clearAt && - statusIsUserDefined == status1.statusIsUserDefined && - Objects.equals(userId, status1.userId) && Objects.equals(message, status1.message) && - Objects.equals(messageId, status1.messageId) && Objects.equals(icon, status1.icon) && - Objects.equals(status, status1.status); - } - - @Override - public int hashCode() { - return Objects.hash(userId, message, messageId, messageIsPredefined, icon, clearAt, status, statusIsUserDefined); - } - - @Override - public String toString() { - return "Status{" + - "userId='" + userId + '\'' + - ", message='" + message + '\'' + - ", messageId='" + messageId + '\'' + - ", messageIsPredefined=" + messageIsPredefined + - ", icon='" + icon + '\'' + - ", clearAt=" + clearAt + - ", status='" + status + '\'' + - ", statusIsUserDefined=" + statusIsUserDefined + - '}'; - } -} diff --git a/app/src/main/java/com/nextcloud/talk/models/json/status/Status.kt b/app/src/main/java/com/nextcloud/talk/models/json/status/Status.kt new file mode 100644 index 000000000..f66a09724 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/models/json/status/Status.kt @@ -0,0 +1,52 @@ +/* + * + * Nextcloud Talk application + * + * @author Tim Krüger + * Copyright (C) 2021 Tim Krüger + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.nextcloud.talk.models.json.status + +import android.os.Parcelable +import com.bluelinelabs.logansquare.annotation.JsonField +import com.bluelinelabs.logansquare.annotation.JsonObject +import kotlinx.android.parcel.Parcelize + +@Parcelize +@JsonObject +data class Status( + @JsonField(name = ["userId"]) + var userId: String?, + @JsonField(name = ["message"]) + var message: String?, + /* TODO: Change to enum */ + @JsonField(name = ["messageId"]) + var messageId: String?, + @JsonField(name = ["messageIsPredefined"]) + var messageIsPredefined: Boolean, + @JsonField(name = ["icon"]) + var icon: String?, + @JsonField(name = ["clearAt"]) + var clearAt: Long = 0, + /* TODO: Change to enum */ + @JsonField(name = ["status"]) + var status: String = "offline", + @JsonField(name = ["statusIsUserDefined"]) + var statusIsUserDefined: Boolean +) : Parcelable { + // This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject' + constructor() : this(null, null, null, false, null, 0, "offline", false) +} diff --git a/app/src/main/java/com/nextcloud/talk/models/json/status/StatusType.kt b/app/src/main/java/com/nextcloud/talk/models/json/status/StatusType.kt new file mode 100644 index 000000000..3f11936de --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/models/json/status/StatusType.kt @@ -0,0 +1,11 @@ +package com.nextcloud.talk.models.json.status + + + +enum class StatusType(val string: String) { + ONLINE("online"), + OFFLINE("offline"), + DND("dnd"), + AWAY("away"), + INVISIBLE("invisible"); +} \ No newline at end of file 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 e497972e7..bdca62698 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 @@ -47,6 +47,7 @@ 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.Status; import com.nextcloud.talk.models.json.status.StatusOverall; import com.nextcloud.talk.utils.ApiUtils; import com.nextcloud.talk.utils.DisplayUtils; @@ -55,6 +56,7 @@ import com.nextcloud.talk.utils.database.user.UserUtils; import java.net.CookieManager; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import javax.inject.Inject; @@ -89,6 +91,8 @@ public class ChooseAccountDialogFragment extends DialogFragment { private FlexibleAdapter adapter; private final List userItems = new ArrayList<>(); + private Status status; + @SuppressLint("InflateParams") @NonNull @Override @@ -154,8 +158,12 @@ public class ChooseAccountDialogFragment extends DialogFragment { binding.setStatus.setOnClickListener(v -> { dismiss(); - SetStatusDialogFragment setStatusDialog = SetStatusDialogFragment.newInstance(user); - setStatusDialog.show(getActivity().getSupportFragmentManager(), "fragment_set_status"); + + // TODO: better solution + if(status != null) { + SetStatusDialogFragment setStatusDialog = SetStatusDialogFragment.newInstance(user, status); + setStatusDialog.show(getActivity().getSupportFragmentManager(), "fragment_set_status"); + } }); if (CapabilitiesUtil.isUserStatusAvailable(userUtils.getCurrentUser())) { @@ -200,28 +208,21 @@ public class ChooseAccountDialogFragment extends DialogFragment { observeOn(AndroidSchedulers.mainThread()). subscribe(new Observer() { - private StatusOverall statusOverall; - @Override - public void onSubscribe(@NonNull Disposable d) { - Log.d("x", "onSubscribe"); - } + public void onSubscribe(@NonNull Disposable d) {} @Override public void onNext(@NonNull StatusOverall statusOverall) { - Log.d("x", "onNext"); - this.statusOverall = statusOverall; + status = statusOverall.ocs.data; } @Override public void onError(@NonNull Throwable e) { - Log.e("x", "Läuft net", e); + Log.e(TAG, "Can't receive user status from server. ", e); } @Override public void onComplete() { - Log.d("x", "complete"); - } }); } 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 index 6ed134c93..94ee5aaf0 100644 --- a/app/src/main/java/com/nextcloud/talk/ui/dialog/SetStatusDialogFragment.kt +++ b/app/src/main/java/com/nextcloud/talk/ui/dialog/SetStatusDialogFragment.kt @@ -22,17 +22,42 @@ package com.nextcloud.talk.ui.dialog import android.annotation.SuppressLint import android.app.Dialog +import android.content.Context import android.os.Bundle +import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.view.inputmethod.InputMethodManager +import android.widget.AdapterView +import android.widget.AdapterView.OnItemSelectedListener +import android.widget.ArrayAdapter import androidx.appcompat.app.AlertDialog import androidx.fragment.app.DialogFragment +import androidx.recyclerview.widget.LinearLayoutManager +import autodagger.AutoInjector +import com.nextcloud.talk.R +import com.nextcloud.talk.adapters.PredefinedStatusClickListener +import com.nextcloud.talk.adapters.PredefinedStatusListAdapter +import com.nextcloud.talk.api.NcApi +import com.nextcloud.talk.application.NextcloudTalkApplication 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 +import com.nextcloud.talk.models.json.generic.GenericOverall +import com.nextcloud.talk.models.json.status.ClearAt +import com.nextcloud.talk.models.json.status.PredefinedStatus +import com.nextcloud.talk.models.json.status.Status +import com.nextcloud.talk.models.json.status.StatusType +import com.nextcloud.talk.utils.ApiUtils +import com.nextcloud.talk.utils.DisplayUtils +import com.vanniktech.emoji.EmojiPopup +import io.reactivex.Observer +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.Disposable +import io.reactivex.schedulers.Schedulers +import java.util.Calendar +import java.util.Locale +import javax.inject.Inject private const val ARG_CURRENT_USER_PARAM = "currentUser" private const val ARG_CURRENT_STATUS_PARAM = "currentStatus" @@ -52,19 +77,23 @@ private const val LAST_HOUR_OF_DAY = 23 private const val LAST_MINUTE_OF_HOUR = 59 private const val LAST_SECOND_OF_MINUTE = 59 +@AutoInjector(NextcloudTalkApplication::class) class SetStatusDialogFragment : - DialogFragment() { + DialogFragment(), PredefinedStatusClickListener { + + private val logTag = ChooseAccountDialogFragment::class.java.simpleName + private lateinit var binding: DialogSetStatusBinding - // private var currentUser: User? = null - // private var currentStatus: Status? = null + private var currentUser: User? = null + private var currentStatus: Status? = null // private lateinit var accountManager: UserAccountManager - // private lateinit var predefinedStatus: ArrayList - // private lateinit var adapter: PredefinedStatusListAdapter - // private var selectedPredefinedMessageId: String? = null - // private var clearAt: Long? = -1 - // private lateinit var popup: EmojiPopup + private lateinit var predefinedStatus: ArrayList + private lateinit var adapter: PredefinedStatusListAdapter + private var selectedPredefinedMessageId: String? = null + private var clearAt: Long? = -1 + private lateinit var popup: EmojiPopup // // @Inject // lateinit var arbitraryDataProvider: ArbitraryDataProvider @@ -75,11 +104,14 @@ class SetStatusDialogFragment : // @Inject // lateinit var clientFactory: ClientFactory + @Inject + lateinit var ncApi: NcApi + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) arguments?.let { - // currentUser = it.getParcelable(ARG_CURRENT_USER_PARAM) - // currentStatus = it.getParcelable(ARG_CURRENT_STATUS_PARAM) + currentUser = it.getParcelable(ARG_CURRENT_USER_PARAM) + currentStatus = it.getParcelable(ARG_CURRENT_STATUS_PARAM) // val json = arbitraryDataProvider.getValue(currentUser, ArbitraryDataProvider.PREDEFINED_STATUS) @@ -91,7 +123,7 @@ class SetStatusDialogFragment : - EmojiManager.install(GoogleEmojiProvider()) + // EmojiManager.install(GoogleEmojiProvider()) } @SuppressLint("InflateParams") @@ -110,82 +142,85 @@ class SetStatusDialogFragment : // accountManager = (activity as BaseActivity).userAccountManager // - // currentStatus?.let { - // binding.emoji.setText(it.icon) - // binding.customStatusInput.text?.clear() - // binding.customStatusInput.setText(it.message) - // visualizeStatus(it.status) - // - // if (it.clearAt > 0) { - // binding.clearStatusAfterSpinner.visibility = View.GONE - // binding.remainingClearTime.apply { - // binding.clearStatusMessageTextView.text = getString(R.string.clear_status_message) - // visibility = View.VISIBLE - // text = DisplayUtils.getRelativeTimestamp(context, it.clearAt * ONE_SECOND_IN_MILLIS, true) - // .toString() - // .decapitalize(Locale.getDefault()) - // setOnClickListener { - // visibility = View.GONE - // binding.clearStatusAfterSpinner.visibility = View.VISIBLE - // binding.clearStatusMessageTextView.text = getString(R.string.clear_status_message_after) - // } - // } - // } - // } - // - // adapter = PredefinedStatusListAdapter(this, requireContext()) - // if (this::predefinedStatus.isInitialized) { - // adapter.list = predefinedStatus - // } - // binding.predefinedStatusList.adapter = adapter - // binding.predefinedStatusList.layoutManager = LinearLayoutManager(context) - // - // binding.onlineStatus.setOnClickListener { setStatus(StatusType.ONLINE) } - // binding.dndStatus.setOnClickListener { setStatus(StatusType.DND) } - // binding.awayStatus.setOnClickListener { setStatus(StatusType.AWAY) } - // binding.invisibleStatus.setOnClickListener { setStatus(StatusType.INVISIBLE) } - // - // binding.clearStatus.setOnClickListener { clearStatus() } - // binding.setStatus.setOnClickListener { setStatusMessage() } - // binding.emoji.setOnClickListener { openEmojiPopup() } - // - // popup = EmojiPopup.Builder - // .fromRootView(view) - // .setOnEmojiClickListener { _, _ -> - // popup.dismiss() - // binding.emoji.clearFocus() - // val imm: InputMethodManager = context?.getSystemService(Context.INPUT_METHOD_SERVICE) as - // InputMethodManager - // imm.hideSoftInputFromWindow(binding.emoji.windowToken, 0) - // } - // .build(binding.emoji) - // binding.emoji.disableKeyboardInput(popup) - // binding.emoji.forceSingleEmoji() - // - // val adapter = ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item) - // adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) - // adapter.add(getString(R.string.dontClear)) - // adapter.add(getString(R.string.thirtyMinutes)) - // adapter.add(getString(R.string.oneHour)) - // adapter.add(getString(R.string.fourHours)) - // adapter.add(getString(R.string.today)) - // adapter.add(getString(R.string.thisWeek)) - // - // binding.clearStatusAfterSpinner.apply { - // this.adapter = adapter - // onItemSelectedListener = object : OnItemSelectedListener { - // override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, id: Long) { - // setClearStatusAfterValue(position) - // } - // - // override fun onNothingSelected(parent: AdapterView<*>?) { - // // nothing to do - // } - // } - // } - // - // binding.clearStatus.setTextColor(ThemeColorUtils.primaryColor(context, true)) + currentStatus?.let { + binding.emoji.setText(it.icon) + binding.customStatusInput.text?.clear() + binding.customStatusInput.setText(it.message) + visualizeStatus(it.status) + + if (it.clearAt > 0) { + binding.clearStatusAfterSpinner.visibility = View.GONE + binding.remainingClearTime.apply { + binding.clearStatusMessageTextView.text = getString(R.string.clear_status_message) + visibility = View.VISIBLE + text = DisplayUtils.getRelativeTimestamp(context, it.clearAt * ONE_SECOND_IN_MILLIS, true) + .toString() + .decapitalize(Locale.getDefault()) + setOnClickListener { + visibility = View.GONE + binding.clearStatusAfterSpinner.visibility = View.VISIBLE + binding.clearStatusMessageTextView.text = getString(R.string.clear_status_message_after) + } + } + } + } + + adapter = PredefinedStatusListAdapter(this, requireContext()) + if (this::predefinedStatus.isInitialized) { + adapter.list = predefinedStatus + } + binding.predefinedStatusList.adapter = adapter + binding.predefinedStatusList.layoutManager = LinearLayoutManager(context) + + binding.onlineStatus.setOnClickListener { setStatus(StatusType.ONLINE) } + binding.dndStatus.setOnClickListener { setStatus(StatusType.DND) } + binding.awayStatus.setOnClickListener { setStatus(StatusType.AWAY) } + binding.invisibleStatus.setOnClickListener { setStatus(StatusType.INVISIBLE) } + + binding.clearStatus.setOnClickListener { clearStatus() } + binding.setStatus.setOnClickListener { setStatusMessage() } + binding.emoji.setOnClickListener { openEmojiPopup() } + + popup = EmojiPopup.Builder + .fromRootView(view) + .setOnEmojiClickListener { _, _ -> + popup.dismiss() + binding.emoji.clearFocus() + val imm: InputMethodManager = context?.getSystemService(Context.INPUT_METHOD_SERVICE) as + InputMethodManager + imm.hideSoftInputFromWindow(binding.emoji.windowToken, 0) + } + .build(binding.emoji) + binding.emoji.disableKeyboardInput(popup) + binding.emoji.forceSingleEmoji() + + val adapter = ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item) + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) + adapter.add(getString(R.string.dontClear)) + adapter.add(getString(R.string.thirtyMinutes)) + adapter.add(getString(R.string.oneHour)) + adapter.add(getString(R.string.fourHours)) + adapter.add(getString(R.string.today)) + adapter.add(getString(R.string.thisWeek)) + + binding.clearStatusAfterSpinner.apply { + this.adapter = adapter + onItemSelectedListener = object : OnItemSelectedListener { + override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, id: Long) { + setClearStatusAfterValue(position) + } + + override fun onNothingSelected(parent: AdapterView<*>?) { + // nothing to do + } + } + } + + binding.clearStatus.setTextColor(resources.getColor(R.color.colorPrimary)) + binding.setStatus.setBackgroundColor(resources.getColor(R.color.colorPrimary)) // ThemeButtonUtils.colorPrimaryButton(binding.setStatus, context) + + binding.customStatusInput.highlightColor = resources.getColor(R.color.colorPrimary) // ThemeTextInputUtils.colorTextInput( // binding.customStatusInputContainer, // binding.customStatusInput, @@ -193,180 +228,202 @@ class SetStatusDialogFragment : // ) } - // @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) } - // ) + @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) } + // ) + + val credentials = ApiUtils.getCredentials(currentUser?.username, currentUser?.token) + ncApi.statusDeleteMessage(credentials, ApiUtils.getUrlForStatus(currentUser?.baseUrl)).subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()).subscribe(object : Observer { + override fun onSubscribe(d: Disposable) {} + override fun onNext(statusOverall: GenericOverall) {} + override fun onError(e: Throwable) { + Log.e(logTag, "Error removing attendee from conversation", e) + } + override fun onComplete() { + dismiss() + } + }) + } + + private fun setStatus(statusType: StatusType) { + visualizeStatus(statusType) + + // asyncRunner.postQuickTask( + // SetStatusTask( + // statusType, + // accountManager.currentOwnCloudAccount?.savedAccount, + // context + // ), + // { + // if (!it) { + // clearTopStatus() + // } + // }, + // { clearTopStatus() } + // ) + } + + private fun visualizeStatus(statusType: String) { + StatusType.values().firstOrNull { it.name == statusType.uppercase(Locale.ROOT) }?.let { visualizeStatus(it) } + } + + private fun visualizeStatus(statusType: StatusType) { + when (statusType) { + StatusType.ONLINE -> { + clearTopStatus() + binding.onlineStatus.setBackgroundColor(resources.getColor(R.color.colorPrimary)) + } + StatusType.AWAY -> { + clearTopStatus() + binding.awayStatus.setBackgroundColor(resources.getColor(R.color.colorPrimary)) + } + StatusType.DND -> { + clearTopStatus() + binding.dndStatus.setBackgroundColor(resources.getColor(R.color.colorPrimary)) + } + StatusType.INVISIBLE -> { + clearTopStatus() + binding.invisibleStatus.setBackgroundColor(resources.getColor(R.color.colorPrimary)) + } + 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() // } // } - private fun dismiss(boolean: Boolean) { - if (boolean) { - dismiss() - } - } + /** * Fragment creator */ companion object { @JvmStatic - fun newInstance(user: User): SetStatusDialogFragment { + fun newInstance(user: User, status: Status): SetStatusDialogFragment { val args = Bundle() args.putParcelable(ARG_CURRENT_USER_PARAM, user) - //args.putParcelable(ARG_CURRENT_STATUS_PARAM, status) + args.putParcelable(ARG_CURRENT_STATUS_PARAM, status) + val dialogFragment = SetStatusDialogFragment() dialogFragment.arguments = args @@ -379,42 +436,45 @@ class SetStatusDialogFragment : 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) - // } + override fun onClick(predefinedStatus: PredefinedStatus) { + selectedPredefinedMessageId = predefinedStatus.id + clearAt = clearAtToUnixTime(predefinedStatus.clearAt) + binding.emoji.setText(predefinedStatus.icon) + binding.customStatusInput.text?.clear() + binding.customStatusInput.text?.append(predefinedStatus.message) + + binding.remainingClearTime.visibility = View.GONE + binding.clearStatusAfterSpinner.visibility = View.VISIBLE + binding.clearStatusMessageTextView.text = getString(R.string.clear_status_message_after) + + if (predefinedStatus.clearAt == null) { + binding.clearStatusAfterSpinner.setSelection(0) + } else { + val clearAt = predefinedStatus.clearAt!! + if (clearAt.type.equals("period")) { + when (clearAt.time) { + "1800" -> binding.clearStatusAfterSpinner.setSelection(POS_HALF_AN_HOUR) + "3600" -> binding.clearStatusAfterSpinner.setSelection(POS_AN_HOUR) + "14400" -> binding.clearStatusAfterSpinner.setSelection(POS_FOUR_HOURS) + else -> binding.clearStatusAfterSpinner.setSelection(POS_DONT_CLEAR) + } + } else if (clearAt.type.equals("end-of")) { + when (clearAt.time) { + "day" -> binding.clearStatusAfterSpinner.setSelection(POS_TODAY) + "week" -> binding.clearStatusAfterSpinner.setSelection(POS_END_OF_WEEK) + else -> binding.clearStatusAfterSpinner.setSelection(POS_DONT_CLEAR) + } + } + } + setClearStatusAfterValue(binding.clearStatusAfterSpinner.selectedItemPosition) + } // // @VisibleForTesting // fun setPredefinedStatus(predefinedStatus: ArrayList) { // adapter.list = predefinedStatus // binding.predefinedStatusList.adapter?.notifyDataSetChanged() // } + + + } diff --git a/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java b/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java index 94ed1ac0d..227f25425 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java +++ b/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java @@ -424,4 +424,8 @@ public class ApiUtils { public static String getUrlForStatus(String baseUrl) { return baseUrl + ocsApiVersion + "/apps/user_status/api/v1/user_status"; } + + public static String getUrlForStatusMessage(String baseUrl) { + return getUrlForStatus(baseUrl) + "/message"; + } } diff --git a/app/src/main/java/com/nextcloud/talk/utils/DisplayUtils.java b/app/src/main/java/com/nextcloud/talk/utils/DisplayUtils.java index 7309ad701..efcd91436 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/DisplayUtils.java +++ b/app/src/main/java/com/nextcloud/talk/utils/DisplayUtils.java @@ -44,6 +44,7 @@ import android.text.SpannableString; import android.text.Spanned; import android.text.TextPaint; import android.text.TextUtils; +import android.text.format.DateUtils; import android.text.method.LinkMovementMethod; import android.text.style.AbsoluteSizeSpan; import android.text.style.ClickableSpan; @@ -86,6 +87,8 @@ import org.greenrobot.eventbus.EventBus; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.text.DateFormat; +import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.regex.Matcher; @@ -124,6 +127,8 @@ public class DisplayUtils { private static final String HTTP_PROTOCOL = "http://"; private static final String HTTPS_PROTOCOL = "https://"; + private static final int DATE_TIME_PARTS_SIZE = 2; + public static void setClickableString(String string, String url, TextView textView) { SpannableString spannableString = new SpannableString(string); spannableString.setSpan(new ClickableSpan() { @@ -605,4 +610,66 @@ public class DisplayUtils { return R.string.menu_item_sort_by_name_a_z; } } + + /** + * calculates the relative time string based on the given modification timestamp. + * + * @param context the app's context + * @param modificationTimestamp the UNIX timestamp of the file modification time in milliseconds. + * @return a relative time string + */ + + public static CharSequence getRelativeTimestamp(Context context, long modificationTimestamp, boolean showFuture) { + return getRelativeDateTimeString(context, + modificationTimestamp, + android.text.format.DateUtils.SECOND_IN_MILLIS, + DateUtils.WEEK_IN_MILLIS, + 0, + showFuture); + } + + public static CharSequence getRelativeDateTimeString(Context c, + long time, + long minResolution, + long transitionResolution, + int flags, + boolean showFuture) { + + CharSequence dateString = ""; + + // in Future + if (!showFuture && time > System.currentTimeMillis()) { + return DisplayUtils.unixTimeToHumanReadable(time); + } + // < 60 seconds -> seconds ago + long diff = System.currentTimeMillis() - time; + if (diff > 0 && diff < 60 * 1000 && minResolution == DateUtils.SECOND_IN_MILLIS) { + return c.getString(R.string.secondsAgo); + } else { + dateString = DateUtils.getRelativeDateTimeString(c, time, minResolution, transitionResolution, flags); + } + + String[] parts = dateString.toString().split(","); + if (parts.length == DATE_TIME_PARTS_SIZE) { + if (parts[1].contains(":") && !parts[0].contains(":")) { + return parts[0]; + } else if (parts[0].contains(":") && !parts[1].contains(":")) { + return parts[1]; + } + } + // dateString contains unexpected format. fallback: use relative date time string from android api as is. + return dateString.toString(); + } + + /** + * Converts Unix time to human readable format + * + * @param milliseconds that have passed since 01/01/1970 + * @return The human readable time for the users locale + */ + public static String unixTimeToHumanReadable(long milliseconds) { + Date date = new Date(milliseconds); + DateFormat df = DateFormat.getDateTimeInstance(); + return df.format(date); + } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 05c9d63d2..95d700d88 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -288,6 +288,7 @@ 1 hour 4 hours This week + seconds ago Unread mentions