Adopt more parts from files app [WIP]

Signed-off-by: Tim Krüger <t@timkrueger.me>
This commit is contained in:
Tim Krüger 2022-01-12 17:33:37 +01:00 committed by Marcel Hibbe
parent 05586ccf47
commit a94f0f1bf1
No known key found for this signature in database
GPG Key ID: C793F8B59F43CE7B
16 changed files with 647 additions and 458 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,4 @@
package com.nextcloud.talk.models.json.status
class ClearAt(val type: String, val time: String)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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,93 +228,107 @@ 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() {
@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)
//
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,
@ -293,41 +342,46 @@ class SetStatusDialogFragment :
// },
// { 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() {
}
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(
@ -350,23 +404,26 @@ class SetStatusDialogFragment :
// { 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<PredefinedStatus>) {
// adapter.list = predefinedStatus
// binding.predefinedStatusList.adapter?.notifyDataSetChanged()
// }
}

View File

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

View File

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

View File

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