Browse Source

Adopt more parts from files app [WIP]

Signed-off-by: Tim Krüger <t@timkrueger.me>
Tim Krüger 3 years ago
parent
commit
a94f0f1bf1

+ 3 - 1
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}"

+ 29 - 0
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 <https://www.gnu.org/licenses/>.
+ */
+
+package com.nextcloud.talk.adapters
+
+import com.nextcloud.talk.models.json.status.PredefinedStatus
+
+interface PredefinedStatusClickListener {
+    fun onClick(predefinedStatus: PredefinedStatus)
+}

+ 50 - 0
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 <https://www.gnu.org/licenses/>.
+ */
+
+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<PredefinedStatusViewHolder>() {
+    internal var list: List<PredefinedStatus> = 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
+    }
+}

+ 59 - 0
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 <https://www.gnu.org/licenses/>.
+ */
+
+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)
+                }
+            }
+        }
+    }
+}

+ 5 - 0
app/src/main/java/com/nextcloud/talk/api/NcApi.java

@@ -450,4 +450,9 @@ public interface NcApi {
      */
     @GET
     Observable<StatusOverall> status(@Header("Authorization") String authorization, @Url String url);
+
+    @DELETE
+    Observable<GenericOverall> statusDeleteMessage(@Header("Authorization") String authorization, @Url String url);
+
+
 }

+ 2 - 2
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)
     }

+ 4 - 0
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)

+ 3 - 0
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?)

+ 0 - 159
app/src/main/java/com/nextcloud/talk/models/json/status/Status.java

@@ -1,159 +0,0 @@
-/*
- *
- *   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 +
-            '}';
-    }
-}

+ 52 - 0
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 <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 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)
+}

+ 11 - 0
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");
+}

+ 13 - 12
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<AdvancedUserItem> adapter;
     private final List<AdvancedUserItem> 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<StatusOverall>() {
 
-                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");
-
                 }
             });
     }

+ 344 - 284
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<PredefinedStatus>
-    // 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<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
@@ -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<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))
+        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(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
+            }
 
-    private fun dismiss(boolean: Boolean) {
-        if (boolean) {
-            dismiss()
+            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<GenericOverall> {
+                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()
+    //     }
+    // }
+
+
+
     /**
      * 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<PredefinedStatus>) {
     //     adapter.list = predefinedStatus
     //     binding.predefinedStatusList.adapter?.notifyDataSetChanged()
     // }
+
+
+
 }

+ 4 - 0
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";
+    }
 }

+ 67 - 0
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);
+    }
 }

+ 1 - 0
app/src/main/res/values/strings.xml

@@ -288,6 +288,7 @@
     <string name="oneHour">1 hour</string>
     <string name="fourHours">4 hours</string>
     <string name="thisWeek">This week</string>
+    <string name="secondsAgo">seconds ago</string>
 
     <!-- Conversations List-->
     <string name="nc_new_mention">Unread mentions</string>