123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455 |
- /*
- * Nextcloud Android client application
- *
- * @author Tobias Kaminsky
- * Copyright (C) 2020 Nextcloud GmbH
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
- *
- * You should have received a copy of the GNU Affero General Public
- * License along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
- package com.nextcloud.ui
- import android.annotation.SuppressLint
- import android.app.Dialog
- import android.content.Context
- import android.os.Bundle
- 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.annotation.VisibleForTesting
- import androidx.appcompat.app.AlertDialog
- import androidx.fragment.app.DialogFragment
- import androidx.recyclerview.widget.LinearLayoutManager
- import com.google.gson.Gson
- import com.google.gson.reflect.TypeToken
- import com.nextcloud.client.account.User
- import com.nextcloud.client.account.UserAccountManager
- import com.nextcloud.client.core.AsyncRunner
- import com.nextcloud.client.di.Injectable
- import com.nextcloud.client.network.ClientFactory
- import com.owncloud.android.R
- import com.owncloud.android.databinding.DialogSetStatusBinding
- import com.owncloud.android.datamodel.ArbitraryDataProvider
- import com.owncloud.android.lib.resources.users.ClearAt
- import com.owncloud.android.lib.resources.users.PredefinedStatus
- import com.owncloud.android.lib.resources.users.Status
- import com.owncloud.android.lib.resources.users.StatusType
- import com.owncloud.android.ui.activity.BaseActivity
- import com.owncloud.android.ui.adapter.PredefinedStatusClickListener
- import com.owncloud.android.ui.adapter.PredefinedStatusListAdapter
- import com.owncloud.android.utils.DisplayUtils
- import com.owncloud.android.utils.theme.ThemeButtonUtils
- import com.owncloud.android.utils.theme.ThemeColorUtils
- import com.owncloud.android.utils.theme.ThemeTextInputUtils
- import com.vanniktech.emoji.EmojiManager
- import com.vanniktech.emoji.EmojiPopup
- import com.vanniktech.emoji.google.GoogleEmojiProvider
- 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"
- private const val POS_DONT_CLEAR = 0
- private const val POS_HALF_AN_HOUR = 1
- private const val POS_AN_HOUR = 2
- private const val POS_FOUR_HOURS = 3
- private const val POS_TODAY = 4
- private const val POS_END_OF_WEEK = 5
- private const val ONE_SECOND_IN_MILLIS = 1000
- private const val ONE_MINUTE_IN_SECONDS = 60
- private const val THIRTY_MINUTES = 30
- private const val FOUR_HOURS = 4
- private const val LAST_HOUR_OF_DAY = 23
- private const val LAST_MINUTE_OF_HOUR = 59
- private const val LAST_SECOND_OF_MINUTE = 59
- class SetStatusDialogFragment :
- DialogFragment(),
- PredefinedStatusClickListener,
- Injectable {
- private lateinit var binding: DialogSetStatusBinding
- private var currentUser: User? = null
- private var currentStatus: Status? = null
- private lateinit var accountManager: UserAccountManager
- private lateinit var predefinedStatus: ArrayList<PredefinedStatus>
- private lateinit var adapter: PredefinedStatusListAdapter
- private var selectedPredefinedMessageId: String? = null
- private var clearAt: Long? = -1
- private lateinit var popup: EmojiPopup
- @Inject
- lateinit var arbitraryDataProvider: ArbitraryDataProvider
- @Inject
- lateinit var asyncRunner: AsyncRunner
- @Inject
- lateinit var clientFactory: ClientFactory
- @Inject
- lateinit var themeColorUtils: ThemeColorUtils
- @Inject
- lateinit var themeButtonUtils: ThemeButtonUtils
- @Inject
- lateinit var themeTextInputUtils: ThemeTextInputUtils
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- arguments?.let {
- currentUser = it.getParcelable(ARG_CURRENT_USER_PARAM)
- currentStatus = it.getParcelable(ARG_CURRENT_STATUS_PARAM)
- val json = arbitraryDataProvider.getValue(currentUser, ArbitraryDataProvider.PREDEFINED_STATUS)
- if (json.isNotEmpty()) {
- val myType = object : TypeToken<ArrayList<PredefinedStatus>>() {}.type
- predefinedStatus = Gson().fromJson(json, myType)
- }
- }
- EmojiManager.install(GoogleEmojiProvider())
- }
- @SuppressLint("InflateParams")
- override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
- binding = DialogSetStatusBinding.inflate(layoutInflater)
- return AlertDialog.Builder(requireContext())
- .setView(binding.root)
- .create()
- }
- @SuppressLint("DefaultLocale")
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
- accountManager = (activity as BaseActivity).userAccountManager
- currentStatus?.let {
- binding.emoji.setText(it.icon)
- binding.customStatusInput.text?.clear()
- binding.customStatusInput.setText(it.message)
- visualizeStatus(it.status)
- if (it.clearAt > 0) {
- binding.clearStatusAfterSpinner.visibility = View.GONE
- binding.remainingClearTime.apply {
- binding.clearStatusMessageTextView.text = getString(R.string.clear_status_message)
- visibility = View.VISIBLE
- text = DisplayUtils.getRelativeTimestamp(context, it.clearAt * ONE_SECOND_IN_MILLIS, true)
- .toString()
- .decapitalize(Locale.getDefault())
- setOnClickListener {
- visibility = View.GONE
- binding.clearStatusAfterSpinner.visibility = View.VISIBLE
- binding.clearStatusMessageTextView.text = getString(R.string.clear_status_message_after)
- }
- }
- }
- }
- adapter = PredefinedStatusListAdapter(this, requireContext())
- if (this::predefinedStatus.isInitialized) {
- adapter.list = predefinedStatus
- }
- binding.predefinedStatusList.adapter = adapter
- binding.predefinedStatusList.layoutManager = LinearLayoutManager(context)
- binding.onlineStatus.setOnClickListener { setStatus(StatusType.ONLINE) }
- binding.dndStatus.setOnClickListener { setStatus(StatusType.DND) }
- binding.awayStatus.setOnClickListener { setStatus(StatusType.AWAY) }
- binding.invisibleStatus.setOnClickListener { setStatus(StatusType.INVISIBLE) }
- binding.clearStatus.setOnClickListener { clearStatus() }
- binding.setStatus.setOnClickListener { setStatusMessage() }
- binding.emoji.setOnClickListener { openEmojiPopup() }
- popup = EmojiPopup.Builder
- .fromRootView(view)
- .setOnEmojiClickListener { _, _ ->
- popup.dismiss()
- binding.emoji.clearFocus()
- val imm: InputMethodManager = context?.getSystemService(Context.INPUT_METHOD_SERVICE) as
- InputMethodManager
- imm.hideSoftInputFromWindow(binding.emoji.windowToken, 0)
- }
- .build(binding.emoji)
- binding.emoji.disableKeyboardInput(popup)
- binding.emoji.forceSingleEmoji()
- val adapter = ArrayAdapter<String>(requireContext(), android.R.layout.simple_spinner_item)
- adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
- adapter.add(getString(R.string.dontClear))
- adapter.add(getString(R.string.thirtyMinutes))
- adapter.add(getString(R.string.oneHour))
- adapter.add(getString(R.string.fourHours))
- adapter.add(getString(R.string.today))
- adapter.add(getString(R.string.thisWeek))
- binding.clearStatusAfterSpinner.apply {
- this.adapter = adapter
- onItemSelectedListener = object : OnItemSelectedListener {
- override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, id: Long) {
- setClearStatusAfterValue(position)
- }
- override fun onNothingSelected(parent: AdapterView<*>?) {
- // nothing to do
- }
- }
- }
- binding.clearStatus.setTextColor(themeColorUtils.primaryColor(context, true))
- themeButtonUtils.colorPrimaryButton(binding.setStatus, context, themeColorUtils)
- themeTextInputUtils.colorTextInput(
- binding.customStatusInputContainer,
- binding.customStatusInput,
- themeColorUtils.primaryColor(activity)
- )
- }
- @Suppress("ComplexMethod")
- private fun setClearStatusAfterValue(item: Int) {
- when (item) {
- POS_DONT_CLEAR -> {
- // don't clear
- clearAt = null
- }
- POS_HALF_AN_HOUR -> {
- // 30 minutes
- clearAt = System.currentTimeMillis() / ONE_SECOND_IN_MILLIS + THIRTY_MINUTES * ONE_MINUTE_IN_SECONDS
- }
- POS_AN_HOUR -> {
- // one hour
- clearAt =
- System.currentTimeMillis() / ONE_SECOND_IN_MILLIS + ONE_MINUTE_IN_SECONDS * ONE_MINUTE_IN_SECONDS
- }
- POS_FOUR_HOURS -> {
- // four hours
- clearAt =
- System.currentTimeMillis() / ONE_SECOND_IN_MILLIS
- +FOUR_HOURS * ONE_MINUTE_IN_SECONDS * ONE_MINUTE_IN_SECONDS
- }
- POS_TODAY -> {
- // today
- val date = Calendar.getInstance().apply {
- set(Calendar.HOUR_OF_DAY, LAST_HOUR_OF_DAY)
- set(Calendar.MINUTE, LAST_MINUTE_OF_HOUR)
- set(Calendar.SECOND, LAST_SECOND_OF_MINUTE)
- }
- clearAt = date.timeInMillis / ONE_SECOND_IN_MILLIS
- }
- POS_END_OF_WEEK -> {
- // end of week
- val date = Calendar.getInstance().apply {
- set(Calendar.HOUR_OF_DAY, LAST_HOUR_OF_DAY)
- set(Calendar.MINUTE, LAST_MINUTE_OF_HOUR)
- set(Calendar.SECOND, LAST_SECOND_OF_MINUTE)
- }
- while (date.get(Calendar.DAY_OF_WEEK) != Calendar.SUNDAY) {
- date.add(Calendar.DAY_OF_YEAR, 1)
- }
- clearAt = date.timeInMillis / ONE_SECOND_IN_MILLIS
- }
- }
- }
- @Suppress("ReturnCount")
- private fun clearAtToUnixTime(clearAt: ClearAt?): Long {
- if (clearAt != null) {
- if (clearAt.type.equals("period")) {
- return System.currentTimeMillis() / ONE_SECOND_IN_MILLIS + clearAt.time.toLong()
- } else if (clearAt.type.equals("end-of")) {
- if (clearAt.time.equals("day")) {
- val date = Calendar.getInstance().apply {
- set(Calendar.HOUR_OF_DAY, LAST_HOUR_OF_DAY)
- set(Calendar.MINUTE, LAST_MINUTE_OF_HOUR)
- set(Calendar.SECOND, LAST_SECOND_OF_MINUTE)
- }
- return date.timeInMillis / ONE_SECOND_IN_MILLIS
- }
- }
- }
- return -1
- }
- private fun openEmojiPopup() {
- popup.show()
- }
- private fun clearStatus() {
- asyncRunner.postQuickTask(
- ClearStatusTask(accountManager.currentOwnCloudAccount?.savedAccount, context),
- { dismiss(it) }
- )
- }
- private fun setStatus(statusType: StatusType) {
- visualizeStatus(statusType)
- asyncRunner.postQuickTask(
- SetStatusTask(
- statusType,
- accountManager.currentOwnCloudAccount?.savedAccount,
- context
- ),
- {
- if (!it) {
- clearTopStatus()
- }
- },
- { clearTopStatus() }
- )
- }
- private fun visualizeStatus(statusType: StatusType) {
- when (statusType) {
- StatusType.ONLINE -> {
- clearTopStatus()
- binding.onlineStatus.setBackgroundColor(themeColorUtils.primaryColor(context))
- }
- StatusType.AWAY -> {
- clearTopStatus()
- binding.awayStatus.setBackgroundColor(themeColorUtils.primaryColor(context))
- }
- StatusType.DND -> {
- clearTopStatus()
- binding.dndStatus.setBackgroundColor(themeColorUtils.primaryColor(context))
- }
- StatusType.INVISIBLE -> {
- clearTopStatus()
- binding.invisibleStatus.setBackgroundColor(themeColorUtils.primaryColor(context))
- }
- else -> clearTopStatus()
- }
- }
- private fun clearTopStatus() {
- context?.let {
- val grey = it.resources.getColor(R.color.grey_200)
- binding.onlineStatus.setBackgroundColor(grey)
- binding.awayStatus.setBackgroundColor(grey)
- binding.dndStatus.setBackgroundColor(grey)
- binding.invisibleStatus.setBackgroundColor(grey)
- }
- }
- private fun setStatusMessage() {
- if (selectedPredefinedMessageId != null) {
- asyncRunner.postQuickTask(
- SetPredefinedCustomStatusTask(
- selectedPredefinedMessageId!!,
- clearAt,
- accountManager.currentOwnCloudAccount?.savedAccount,
- context
- ),
- { dismiss(it) }
- )
- } else {
- asyncRunner.postQuickTask(
- SetUserDefinedCustomStatusTask(
- binding.customStatusInput.text.toString(),
- binding.emoji.text.toString(),
- clearAt,
- accountManager.currentOwnCloudAccount?.savedAccount,
- context
- ),
- { dismiss(it) }
- )
- }
- }
- private fun dismiss(boolean: Boolean) {
- if (boolean) {
- dismiss()
- }
- }
- /**
- * Fragment creator
- */
- companion object {
- @JvmStatic
- fun newInstance(user: User, status: Status?): SetStatusDialogFragment {
- val args = Bundle()
- args.putParcelable(ARG_CURRENT_USER_PARAM, user)
- args.putParcelable(ARG_CURRENT_STATUS_PARAM, status)
- val dialogFragment = SetStatusDialogFragment()
- dialogFragment.arguments = args
- dialogFragment.setStyle(STYLE_NORMAL, R.style.Theme_ownCloud_Dialog)
- return dialogFragment
- }
- }
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
- return binding.root
- }
- override fun onClick(predefinedStatus: PredefinedStatus) {
- selectedPredefinedMessageId = predefinedStatus.id
- clearAt = clearAtToUnixTime(predefinedStatus.clearAt)
- binding.emoji.setText(predefinedStatus.icon)
- binding.customStatusInput.text?.clear()
- binding.customStatusInput.text?.append(predefinedStatus.message)
- binding.remainingClearTime.visibility = View.GONE
- binding.clearStatusAfterSpinner.visibility = View.VISIBLE
- binding.clearStatusMessageTextView.text = getString(R.string.clear_status_message_after)
- if (predefinedStatus.clearAt == null) {
- binding.clearStatusAfterSpinner.setSelection(0)
- } else {
- val clearAt = predefinedStatus.clearAt!!
- if (clearAt.type.equals("period")) {
- when (clearAt.time) {
- "1800" -> binding.clearStatusAfterSpinner.setSelection(POS_HALF_AN_HOUR)
- "3600" -> binding.clearStatusAfterSpinner.setSelection(POS_AN_HOUR)
- "14400" -> binding.clearStatusAfterSpinner.setSelection(POS_FOUR_HOURS)
- else -> binding.clearStatusAfterSpinner.setSelection(POS_DONT_CLEAR)
- }
- } else if (clearAt.type.equals("end-of")) {
- when (clearAt.time) {
- "day" -> binding.clearStatusAfterSpinner.setSelection(POS_TODAY)
- "week" -> binding.clearStatusAfterSpinner.setSelection(POS_END_OF_WEEK)
- else -> binding.clearStatusAfterSpinner.setSelection(POS_DONT_CLEAR)
- }
- }
- }
- setClearStatusAfterValue(binding.clearStatusAfterSpinner.selectedItemPosition)
- }
- @VisibleForTesting
- fun setPredefinedStatus(predefinedStatus: ArrayList<PredefinedStatus>) {
- adapter.list = predefinedStatus
- binding.predefinedStatusList.adapter?.notifyDataSetChanged()
- }
- }
|