diff --git a/app/build.gradle b/app/build.gradle index ad3915a46..6cfdbf526 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -79,6 +79,7 @@ android { disable 'InvalidPackage' disable 'MissingTranslation' disable 'VectorPath' + disable 'UnusedQuantity' } javaCompileOptions { diff --git a/app/src/main/java/com/nextcloud/talk/activities/TakePhotoActivity.java b/app/src/main/java/com/nextcloud/talk/activities/TakePhotoActivity.java index 5eea1d84c..08b972545 100644 --- a/app/src/main/java/com/nextcloud/talk/activities/TakePhotoActivity.java +++ b/app/src/main/java/com/nextcloud/talk/activities/TakePhotoActivity.java @@ -41,8 +41,10 @@ import android.widget.Toast; import com.google.common.util.concurrent.ListenableFuture; import com.nextcloud.talk.R; +import com.nextcloud.talk.application.NextcloudTalkApplication; import com.nextcloud.talk.databinding.ActivityTakePictureBinding; import com.nextcloud.talk.models.TakePictureViewModel; +import com.nextcloud.talk.ui.theme.ViewThemeUtils; import com.nextcloud.talk.utils.BitmapShrinker; import com.nextcloud.talk.utils.FileUtils; @@ -52,6 +54,8 @@ import java.util.Date; import java.util.Locale; import java.util.concurrent.ExecutionException; +import javax.inject.Inject; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.OptIn; @@ -66,9 +70,11 @@ import androidx.camera.lifecycle.ProcessCameraProvider; import androidx.core.content.ContextCompat; import androidx.exifinterface.media.ExifInterface; import androidx.lifecycle.ViewModelProvider; +import autodagger.AutoInjector; import static com.nextcloud.talk.utils.Mimetype.IMAGE_JPEG; +@AutoInjector(NextcloudTalkApplication.class) public class TakePhotoActivity extends AppCompatActivity { private static final String TAG = TakePhotoActivity.class.getSimpleName(); @@ -86,15 +92,22 @@ public class TakePhotoActivity extends AppCompatActivity { private Camera camera; + @Inject + ViewThemeUtils viewThemeUtils; + @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); + NextcloudTalkApplication.Companion.getSharedApplication().getComponentApplication().inject(this); binding = ActivityTakePictureBinding.inflate(getLayoutInflater()); viewModel = new ViewModelProvider(this).get(TakePictureViewModel.class); setContentView(binding.getRoot()); + viewThemeUtils.themeFAB(binding.takePhoto); + viewThemeUtils.colorMaterialButtonBackground(binding.send); + cameraProviderFuture = ProcessCameraProvider.getInstance(this); cameraProviderFuture.addListener(() -> { try { diff --git a/app/src/main/java/com/nextcloud/talk/adapters/items/ContactItem.java b/app/src/main/java/com/nextcloud/talk/adapters/items/ContactItem.java index a358c931c..5090bc761 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/items/ContactItem.java +++ b/app/src/main/java/com/nextcloud/talk/adapters/items/ContactItem.java @@ -35,6 +35,7 @@ import com.nextcloud.talk.application.NextcloudTalkApplication; import com.nextcloud.talk.data.user.model.User; import com.nextcloud.talk.databinding.RvItemContactBinding; import com.nextcloud.talk.models.json.participants.Participant; +import com.nextcloud.talk.ui.theme.ViewThemeUtils; import com.nextcloud.talk.utils.ApiUtils; import com.nextcloud.talk.utils.DisplayUtils; @@ -59,14 +60,17 @@ public class ContactItem extends AbstractFlexibleItem(), IFilterable, @@ -77,7 +78,7 @@ data class MessageResultItem constructor( private fun bindMessageExcerpt(holder: ViewHolder) { val messageSpannable = SpannableString(messageEntry.messageExcerpt) - val highlightColor = ContextCompat.getColor(context, R.color.colorPrimary) + val highlightColor = viewThemeUtils.getElementColor(holder.binding.messageExcerpt.context) val highlightedSpan = DisplayUtils.searchAndColor(messageSpannable, messageEntry.searchTerm, highlightColor) holder.binding.messageExcerpt.text = highlightedSpan } @@ -104,7 +105,7 @@ data class MessageResultItem constructor( const val VIEW_TYPE: Int = R.layout.rv_item_search_message } - override fun getHeader(): GenericTextHeaderItem = MessagesTextHeaderItem(context) + override fun getHeader(): GenericTextHeaderItem = MessagesTextHeaderItem(context, viewThemeUtils) .apply { isHidden = showHeader // FlexibleAdapter needs this hack for some reason } diff --git a/app/src/main/java/com/nextcloud/talk/adapters/items/MessagesTextHeaderItem.kt b/app/src/main/java/com/nextcloud/talk/adapters/items/MessagesTextHeaderItem.kt index 24ddeabc5..3ade534b6 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/items/MessagesTextHeaderItem.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/items/MessagesTextHeaderItem.kt @@ -23,8 +23,10 @@ package com.nextcloud.talk.adapters.items import android.content.Context import com.nextcloud.talk.R +import com.nextcloud.talk.ui.theme.ViewThemeUtils -class MessagesTextHeaderItem(context: Context) : GenericTextHeaderItem(context.getString(R.string.messages)) { +class MessagesTextHeaderItem(context: Context, viewThemeUtils: ViewThemeUtils) : + GenericTextHeaderItem(context.getString(R.string.messages), viewThemeUtils) { companion object { /** * "Random" value, just has to be different than other view types diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingVoiceMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingVoiceMessageViewHolder.kt index e06604763..8546dea0d 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingVoiceMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingVoiceMessageViewHolder.kt @@ -48,6 +48,7 @@ import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication import com.nextcloud.talk.databinding.ItemCustomIncomingVoiceMessageBinding import com.nextcloud.talk.models.json.chat.ChatMessage +import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.DisplayUtils import com.nextcloud.talk.utils.preferences.AppPreferences @@ -66,6 +67,9 @@ class IncomingVoiceMessageViewHolder(incomingView: View, payload: Any) : Message @Inject var context: Context? = null + @Inject + lateinit var viewThemeUtils: ViewThemeUtils + @JvmField @Inject var appPreferences: AppPreferences? = null @@ -93,6 +97,7 @@ class IncomingVoiceMessageViewHolder(incomingView: View, payload: Any) : Message updateDownloadState(message) binding.seekbar.max = message.voiceMessageDuration + viewThemeUtils.themeHorizontalSeekBar(binding.seekbar) if (message.isPlayingVoiceMessage) { showPlayButton() diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/MagicOutcomingTextMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/MagicOutcomingTextMessageViewHolder.kt index d4508c8e1..6102446e5 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/MagicOutcomingTextMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/MagicOutcomingTextMessageViewHolder.kt @@ -25,14 +25,13 @@ package com.nextcloud.talk.adapters.messages import android.content.Context import android.content.Intent -import android.graphics.PorterDuff import android.net.Uri import android.text.Spannable import android.text.SpannableString import android.util.TypedValue import android.view.View -import androidx.core.content.ContextCompat import androidx.core.content.res.ResourcesCompat +import androidx.core.graphics.ColorUtils import androidx.core.view.ViewCompat import autodagger.AutoInjector import coil.load @@ -44,22 +43,29 @@ import com.nextcloud.talk.databinding.ItemCustomOutcomingTextMessageBinding import com.nextcloud.talk.models.json.chat.ChatMessage import com.nextcloud.talk.models.json.chat.ReadStatus import com.nextcloud.talk.ui.recyclerview.MessageSwipeCallback +import com.nextcloud.talk.ui.theme.ServerTheme +import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.DisplayUtils.getMessageSelector import com.nextcloud.talk.utils.DisplayUtils.searchAndReplaceWithMentionSpan import com.nextcloud.talk.utils.TextMatchers import com.stfalcon.chatkit.messages.MessageHolders.OutcomingTextMessageViewHolder -import java.util.HashMap import javax.inject.Inject +import kotlin.math.roundToInt @AutoInjector(NextcloudTalkApplication::class) class MagicOutcomingTextMessageViewHolder(itemView: View) : OutcomingTextMessageViewHolder(itemView) { private val binding: ItemCustomOutcomingTextMessageBinding = ItemCustomOutcomingTextMessageBinding.bind(itemView) private val realView: View = itemView - @JvmField @Inject - var context: Context? = null + lateinit var context: Context + + @Inject + lateinit var viewThemeUtils: ViewThemeUtils + + @Inject + lateinit var serverTheme: ServerTheme lateinit var reactionsInterface: ReactionsInterface @@ -69,7 +75,7 @@ class MagicOutcomingTextMessageViewHolder(itemView: View) : OutcomingTextMessage val messageParameters: HashMap>? = message.messageParameters var messageString: Spannable = SpannableString(message.text) realView.isSelected = false - binding.messageTime.setTextColor(context!!.resources.getColor(R.color.white60)) + binding.messageTime.setTextColor(ColorUtils.setAlphaComponent(serverTheme.colorText, ALPHA_60_INT)) val layoutParams = binding.messageTime.layoutParams as FlexboxLayout.LayoutParams layoutParams.isWrapBefore = false var textSize = context!!.resources.getDimension(R.dimen.chat_text_size) @@ -89,6 +95,8 @@ class MagicOutcomingTextMessageViewHolder(itemView: View) : OutcomingTextMessage binding.messageText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize) binding.messageTime.layoutParams = layoutParams binding.messageText.text = messageString + binding.messageText.setTextColor(serverTheme.colorText) + binding.messageText.setLinkTextColor(serverTheme.colorText) // parent message handling if (!message.isDeleted && message.parentMessage != null) { @@ -112,8 +120,8 @@ class MagicOutcomingTextMessageViewHolder(itemView: View) : OutcomingTextMessage readStatusDrawableInt?.let { drawableInt -> ResourcesCompat.getDrawable(context!!.resources, drawableInt, null)?.let { - it.setColorFilter(ContextCompat.getColor(context!!, R.color.white60), PorterDuff.Mode.SRC_ATOP) binding.checkMark.setImageDrawable(it) + viewThemeUtils.colorImageViewText(binding.checkMark) } } @@ -148,20 +156,25 @@ class MagicOutcomingTextMessageViewHolder(itemView: View) : OutcomingTextMessage binding.messageQuote.quotedMessageAuthor.text = parentChatMessage.actorDisplayName ?: context!!.getText(R.string.nc_nick_guest) binding.messageQuote.quotedMessage.text = parentChatMessage.text - binding.messageQuote.quotedMessage.setTextColor( - ContextCompat.getColor(context!!, R.color.nc_outcoming_text_default) - ) - binding.messageQuote.quotedMessageAuthor.setTextColor(ContextCompat.getColor(context!!, R.color.nc_grey)) + binding.messageQuote.quotedMessage.setTextColor(serverTheme.colorText) - binding.messageQuote.quoteColoredView.setBackgroundResource(R.color.white) + binding.messageQuote.quotedMessageAuthor.setTextColor( + ColorUtils.setAlphaComponent( + serverTheme.colorText, + ALPHA_80_INT + ) + ) + + binding.messageQuote.quoteColoredView.setBackgroundColor(serverTheme.colorText) } private fun setBubbleOnChatMessage(message: ChatMessage) { val resources = sharedApplication!!.resources + val elementColor = viewThemeUtils.getElementColor(binding.root.context) val bgBubbleColor = if (message.isDeleted) { - ResourcesCompat.getColor(resources, R.color.bg_message_list_outcoming_bubble_deleted, null) + ColorUtils.setAlphaComponent(elementColor, HALF_ALPHA_INT) } else { - ResourcesCompat.getColor(resources, R.color.bg_message_list_outcoming_bubble, null) + elementColor } if (message.isGrouped) { val bubbleDrawable = getMessageSelector( @@ -221,5 +234,8 @@ class MagicOutcomingTextMessageViewHolder(itemView: View) : OutcomingTextMessage companion object { const val TEXT_SIZE_MULTIPLIER = 2.5 + private const val HALF_ALPHA_INT: Int = 255 / 2 + private val ALPHA_60_INT: Int = (255 * 0.6).roundToInt() + private val ALPHA_80_INT: Int = (255 * 0.8).roundToInt() } } diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/MagicPreviewMessageViewHolder.java b/app/src/main/java/com/nextcloud/talk/adapters/messages/MagicPreviewMessageViewHolder.java index 48e33915e..e6c5a7d23 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/MagicPreviewMessageViewHolder.java +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/MagicPreviewMessageViewHolder.java @@ -29,6 +29,7 @@ package com.nextcloud.talk.adapters.messages; import android.annotation.SuppressLint; import android.content.Context; import android.content.Intent; +import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; import android.net.Uri; @@ -49,6 +50,7 @@ import com.nextcloud.talk.components.filebrowser.webdav.ReadFilesystemOperation; import com.nextcloud.talk.data.user.model.User; import com.nextcloud.talk.databinding.ReactionsInsideMessageBinding; import com.nextcloud.talk.models.json.chat.ChatMessage; +import com.nextcloud.talk.ui.theme.ServerTheme; import com.nextcloud.talk.utils.DisplayUtils; import com.nextcloud.talk.utils.DrawableUtils; import com.nextcloud.talk.utils.FileViewerUtils; @@ -91,6 +93,9 @@ public abstract class MagicPreviewMessageViewHolder extends MessageHolders.Incom @Inject Context context; + @Inject + ServerTheme serverTheme; + @Inject OkHttpClient okHttpClient; @@ -175,6 +180,13 @@ public abstract class MagicPreviewMessageViewHolder extends MessageHolders.Incom String mimetype = message.getSelectedIndividualHashMap().get(KEY_MIMETYPE); int drawableResourceId = DrawableUtils.INSTANCE.getDrawableResourceIdForMimeType(mimetype); Drawable drawable = ContextCompat.getDrawable(context, drawableResourceId); + + if (drawable != null && + (drawableResourceId == R.drawable.ic_mimetype_folder || + drawableResourceId == R.drawable.ic_mimetype_package_x_generic)) { + drawable.setColorFilter(serverTheme.getPrimaryColor(), PorterDuff.Mode.SRC_ATOP); + } + image.getHierarchy().setPlaceholderImage(drawable); } else { fetchFileInformation("/" + message.getSelectedIndividualHashMap().get(KEY_PATH), diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingLocationMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingLocationMessageViewHolder.kt index 91fb93583..fecf4af64 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingLocationMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingLocationMessageViewHolder.kt @@ -25,7 +25,6 @@ package com.nextcloud.talk.adapters.messages import android.annotation.SuppressLint import android.content.Context import android.content.Intent -import android.graphics.PorterDuff import android.net.Uri import android.util.Log import android.util.TypedValue @@ -35,6 +34,8 @@ import android.webkit.WebView import android.webkit.WebViewClient import android.widget.Toast import androidx.appcompat.content.res.AppCompatResources +import androidx.core.content.res.ResourcesCompat +import androidx.core.graphics.ColorUtils import androidx.core.view.ViewCompat import autodagger.AutoInjector import coil.load @@ -45,12 +46,15 @@ import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedA import com.nextcloud.talk.databinding.ItemCustomOutcomingLocationMessageBinding import com.nextcloud.talk.models.json.chat.ChatMessage import com.nextcloud.talk.models.json.chat.ReadStatus +import com.nextcloud.talk.ui.theme.ServerTheme +import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.DisplayUtils import com.nextcloud.talk.utils.UriUtils import com.stfalcon.chatkit.messages.MessageHolders import java.net.URLEncoder import javax.inject.Inject +import kotlin.math.roundToInt @AutoInjector(NextcloudTalkApplication::class) class OutcomingLocationMessageViewHolder(incomingView: View) : MessageHolders @@ -68,6 +72,12 @@ class OutcomingLocationMessageViewHolder(incomingView: View) : MessageHolders @Inject var context: Context? = null + @Inject + lateinit var viewThemeUtils: ViewThemeUtils + + @Inject + lateinit var serverTheme: ServerTheme + lateinit var reactionsInterface: ReactionsInterface @SuppressLint("SetTextI18n") @@ -76,7 +86,6 @@ class OutcomingLocationMessageViewHolder(incomingView: View) : MessageHolders sharedApplication!!.componentApplication.inject(this) realView.isSelected = false - binding.messageTime.setTextColor(context!!.resources.getColor(R.color.white60)) val layoutParams = binding.messageTime.layoutParams as FlexboxLayout.LayoutParams layoutParams.isWrapBefore = false @@ -85,7 +94,11 @@ class OutcomingLocationMessageViewHolder(incomingView: View) : MessageHolders colorizeMessageBubble(message) binding.messageText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize) binding.messageTime.layoutParams = layoutParams + binding.messageTime.setTextColor(ColorUtils.setAlphaComponent(serverTheme.colorText, ALPHA_60_INT)) + binding.messageText.text = message.text + binding.messageText.setTextColor(serverTheme.colorText) + binding.messageText.setLinkTextColor(serverTheme.colorText) // parent message handling setParentMessageDataOnMessageItem(message) @@ -104,8 +117,8 @@ class OutcomingLocationMessageViewHolder(incomingView: View) : MessageHolders readStatusDrawableInt?.let { drawableInt -> AppCompatResources.getDrawable(context!!, drawableInt)?.let { - it.setColorFilter(context?.resources!!.getColor(R.color.white60), PorterDuff.Mode.SRC_ATOP) binding.checkMark.setImageDrawable(it) + viewThemeUtils.colorImageViewText(binding.checkMark) } } @@ -200,12 +213,12 @@ class OutcomingLocationMessageViewHolder(incomingView: View) : MessageHolders binding.messageQuote.quotedMessageAuthor.text = parentChatMessage.actorDisplayName ?: context!!.getText(R.string.nc_nick_guest) binding.messageQuote.quotedMessage.text = parentChatMessage.text - binding.messageQuote.quotedMessage.setTextColor( - context!!.resources.getColor(R.color.nc_outcoming_text_default) + binding.messageQuote.quotedMessage.setTextColor(serverTheme.colorText) + binding.messageQuote.quotedMessageAuthor.setTextColor( + ColorUtils.setAlphaComponent(serverTheme.colorText, ALPHA_80_INT) ) - binding.messageQuote.quotedMessageAuthor.setTextColor(context!!.resources.getColor(R.color.nc_grey)) - binding.messageQuote.quoteColoredView.setBackgroundResource(R.color.white) + binding.messageQuote.quoteColoredView.setBackgroundColor(serverTheme.colorText) binding.messageQuote.quotedChatMessageView.visibility = View.VISIBLE } else { @@ -215,15 +228,16 @@ class OutcomingLocationMessageViewHolder(incomingView: View) : MessageHolders private fun colorizeMessageBubble(message: ChatMessage) { val resources = sharedApplication!!.resources + val elementColor = viewThemeUtils.getElementColor(binding.root.context) val bgBubbleColor = if (message.isDeleted) { - resources.getColor(R.color.bg_message_list_outcoming_bubble_deleted) + ColorUtils.setAlphaComponent(elementColor, HALF_ALPHA_INT) } else { - resources.getColor(R.color.bg_message_list_outcoming_bubble) + elementColor } if (message.isGrouped) { val bubbleDrawable = DisplayUtils.getMessageSelector( bgBubbleColor, - resources.getColor(R.color.transparent), + ResourcesCompat.getColor(resources, R.color.transparent, null), bgBubbleColor, R.drawable.shape_grouped_outcoming_message ) @@ -231,7 +245,7 @@ class OutcomingLocationMessageViewHolder(incomingView: View) : MessageHolders } else { val bubbleDrawable = DisplayUtils.getMessageSelector( bgBubbleColor, - resources.getColor(R.color.transparent), + ResourcesCompat.getColor(resources, R.color.transparent, null), bgBubbleColor, R.drawable.shape_outcoming_message ) @@ -261,5 +275,8 @@ class OutcomingLocationMessageViewHolder(incomingView: View) : MessageHolders companion object { private const val TAG = "LocOutMessageView" + private const val HALF_ALPHA_INT: Int = 255 / 2 + private val ALPHA_60_INT: Int = (255 * 0.6).roundToInt() + private val ALPHA_80_INT: Int = (255 * 0.8).roundToInt() } } diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingPollMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingPollMessageViewHolder.kt index 2d1726d57..a418ac686 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingPollMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingPollMessageViewHolder.kt @@ -23,9 +23,11 @@ package com.nextcloud.talk.adapters.messages import android.annotation.SuppressLint import android.content.Context -import android.graphics.PorterDuff +import android.content.res.ColorStateList import android.view.View import androidx.appcompat.content.res.AppCompatResources +import androidx.core.content.res.ResourcesCompat +import androidx.core.graphics.ColorUtils import androidx.core.view.ViewCompat import autodagger.AutoInjector import coil.load @@ -38,11 +40,14 @@ import com.nextcloud.talk.databinding.ItemCustomOutcomingPollMessageBinding import com.nextcloud.talk.models.json.chat.ChatMessage import com.nextcloud.talk.models.json.chat.ReadStatus import com.nextcloud.talk.polls.ui.PollMainDialogFragment +import com.nextcloud.talk.ui.theme.ServerTheme +import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.DisplayUtils import com.nextcloud.talk.utils.preferences.AppPreferences import com.stfalcon.chatkit.messages.MessageHolders import javax.inject.Inject +import kotlin.math.roundToInt @AutoInjector(NextcloudTalkApplication::class) class OutcomingPollMessageViewHolder(outcomingView: View, payload: Any) : MessageHolders @@ -54,6 +59,12 @@ class OutcomingPollMessageViewHolder(outcomingView: View, payload: Any) : Messag @Inject lateinit var context: Context + @Inject + lateinit var viewThemeUtils: ViewThemeUtils + + @Inject + lateinit var serverTheme: ServerTheme + @Inject lateinit var appPreferences: AppPreferences @@ -73,7 +84,12 @@ class OutcomingPollMessageViewHolder(outcomingView: View, payload: Any) : Messag colorizeMessageBubble(message) itemView.isSelected = false - binding.messageTime.setTextColor(context.resources.getColor(R.color.white60)) + binding.messageTime.setTextColor( + ColorUtils.setAlphaComponent( + serverTheme.colorText, + ALPHA_60_INT + ) + ) // parent message handling setParentMessageDataOnMessageItem(message) @@ -92,8 +108,8 @@ class OutcomingPollMessageViewHolder(outcomingView: View, payload: Any) : Messag readStatusDrawableInt?.let { drawableInt -> AppCompatResources.getDrawable(context, drawableInt)?.let { - it.setColorFilter(context.resources!!.getColor(R.color.white60), PorterDuff.Mode.SRC_ATOP) binding.checkMark.setImageDrawable(it) + viewThemeUtils.colorImageViewText(binding.checkMark) } } @@ -126,6 +142,9 @@ class OutcomingPollMessageViewHolder(outcomingView: View, payload: Any) : Messag } if (pollId != null && pollName != null) { + binding.messagePollTitle.setTextColor(serverTheme.colorText) + binding.messagePollSubtitle.setTextColor(serverTheme.colorText) + binding.messagePollIcon.imageTintList = ColorStateList.valueOf(serverTheme.colorText) binding.messagePollTitle.text = pollName val roomToken = (payload as? MessagePayload)!!.roomToken @@ -165,12 +184,12 @@ class OutcomingPollMessageViewHolder(outcomingView: View, payload: Any) : Messag binding.messageQuote.quotedMessageAuthor.text = parentChatMessage.actorDisplayName ?: context.getText(R.string.nc_nick_guest) binding.messageQuote.quotedMessage.text = parentChatMessage.text - binding.messageQuote.quotedMessage.setTextColor( - context.resources.getColor(R.color.nc_outcoming_text_default) + binding.messageQuote.quotedMessage.setTextColor(serverTheme.colorText) + binding.messageQuote.quotedMessageAuthor.setTextColor( + ColorUtils.setAlphaComponent(serverTheme.colorText, ALPHA_80_INT) ) - binding.messageQuote.quotedMessageAuthor.setTextColor(context.resources.getColor(R.color.nc_grey)) - binding.messageQuote.quoteColoredView.setBackgroundResource(R.color.white) + binding.messageQuote.quoteColoredView.setBackgroundColor(serverTheme.colorText) binding.messageQuote.quotedChatMessageView.visibility = View.VISIBLE } else { @@ -180,15 +199,16 @@ class OutcomingPollMessageViewHolder(outcomingView: View, payload: Any) : Messag private fun colorizeMessageBubble(message: ChatMessage) { val resources = sharedApplication!!.resources + val elementColor = viewThemeUtils.getElementColor(binding.root.context) val bgBubbleColor = if (message.isDeleted) { - resources.getColor(R.color.bg_message_list_outcoming_bubble_deleted) + ColorUtils.setAlphaComponent(elementColor, HALF_ALPHA_INT) } else { - resources.getColor(R.color.bg_message_list_outcoming_bubble) + elementColor } if (message.isGrouped) { val bubbleDrawable = DisplayUtils.getMessageSelector( bgBubbleColor, - resources.getColor(R.color.transparent), + ResourcesCompat.getColor(resources, R.color.transparent, null), bgBubbleColor, R.drawable.shape_grouped_outcoming_message ) @@ -196,7 +216,7 @@ class OutcomingPollMessageViewHolder(outcomingView: View, payload: Any) : Messag } else { val bubbleDrawable = DisplayUtils.getMessageSelector( bgBubbleColor, - resources.getColor(R.color.transparent), + ResourcesCompat.getColor(resources, R.color.transparent, null), bgBubbleColor, R.drawable.shape_outcoming_message ) @@ -210,5 +230,8 @@ class OutcomingPollMessageViewHolder(outcomingView: View, payload: Any) : Messag companion object { private val TAG = NextcloudTalkApplication::class.java.simpleName + private val ALPHA_60_INT: Int = (255 * 0.6).roundToInt() + private val ALPHA_80_INT: Int = (255 * 0.8).roundToInt() + private const val HALF_ALPHA_INT: Int = 255 / 2 } } diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingVoiceMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingVoiceMessageViewHolder.kt index b964d3b1f..060897ba8 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingVoiceMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingVoiceMessageViewHolder.kt @@ -31,6 +31,8 @@ import android.view.View import android.widget.SeekBar import androidx.appcompat.content.res.AppCompatResources import androidx.core.content.ContextCompat +import androidx.core.content.res.ResourcesCompat +import androidx.core.graphics.ColorUtils import androidx.core.view.ViewCompat import androidx.work.WorkInfo import androidx.work.WorkManager @@ -42,12 +44,15 @@ import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedA import com.nextcloud.talk.databinding.ItemCustomOutcomingVoiceMessageBinding import com.nextcloud.talk.models.json.chat.ChatMessage import com.nextcloud.talk.models.json.chat.ReadStatus +import com.nextcloud.talk.ui.theme.ServerTheme +import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.DisplayUtils import com.nextcloud.talk.utils.preferences.AppPreferences import com.stfalcon.chatkit.messages.MessageHolders import java.util.concurrent.ExecutionException import javax.inject.Inject +import kotlin.math.roundToInt @AutoInjector(NextcloudTalkApplication::class) class OutcomingVoiceMessageViewHolder(outcomingView: View) : MessageHolders @@ -60,6 +65,12 @@ class OutcomingVoiceMessageViewHolder(outcomingView: View) : MessageHolders @Inject var context: Context? = null + @Inject + lateinit var viewThemeUtils: ViewThemeUtils + + @Inject + lateinit var serverTheme: ServerTheme + @JvmField @Inject var appPreferences: AppPreferences? = null @@ -80,13 +91,19 @@ class OutcomingVoiceMessageViewHolder(outcomingView: View) : MessageHolders colorizeMessageBubble(message) itemView.isSelected = false - binding.messageTime.setTextColor(context!!.resources.getColor(R.color.white60)) + binding.messageTime.setTextColor( + ColorUtils.setAlphaComponent( + serverTheme.colorText, + ALPHA_60_INT + ) + ) // parent message handling setParentMessageDataOnMessageItem(message) updateDownloadState(message) binding.seekbar.max = message.voiceMessageDuration + viewThemeUtils.themeHorizontalSeekBar(binding.seekbar, serverTheme.colorText) handleIsPlayingVoiceMessageState(message) @@ -124,8 +141,8 @@ class OutcomingVoiceMessageViewHolder(outcomingView: View) : MessageHolders readStatusDrawableInt?.let { drawableInt -> AppCompatResources.getDrawable(context!!, drawableInt)?.let { - it.setColorFilter(context?.resources!!.getColor(R.color.white60), PorterDuff.Mode.SRC_ATOP) binding.checkMark.setImageDrawable(it) + viewThemeUtils.colorImageViewText(binding.checkMark) } } @@ -148,6 +165,7 @@ class OutcomingVoiceMessageViewHolder(outcomingView: View) : MessageHolders context!!, R.drawable.ic_baseline_play_arrow_voice_message_24 ) + binding.playPauseBtn.icon.setColorFilter(serverTheme.colorText, PorterDuff.Mode.SRC_ATOP) binding.seekbar.progress = SEEKBAR_START message.resetVoiceMessage = false } @@ -168,6 +186,7 @@ class OutcomingVoiceMessageViewHolder(outcomingView: View) : MessageHolders context!!, R.drawable.ic_baseline_pause_voice_message_24 ) + binding.playPauseBtn.icon.setColorFilter(serverTheme.colorText, PorterDuff.Mode.SRC_ATOP) binding.seekbar.progress = message.voiceMessagePlayedSeconds } else { binding.playPauseBtn.visibility = View.VISIBLE @@ -175,6 +194,7 @@ class OutcomingVoiceMessageViewHolder(outcomingView: View) : MessageHolders context!!, R.drawable.ic_baseline_play_arrow_voice_message_24 ) + binding.playPauseBtn.icon.setColorFilter(serverTheme.colorText, PorterDuff.Mode.SRC_ATOP) } } @@ -250,12 +270,15 @@ class OutcomingVoiceMessageViewHolder(outcomingView: View) : MessageHolders binding.messageQuote.quotedMessageAuthor.text = parentChatMessage.actorDisplayName ?: context!!.getText(R.string.nc_nick_guest) binding.messageQuote.quotedMessage.text = parentChatMessage.text - binding.messageQuote.quotedMessage.setTextColor( - context!!.resources.getColor(R.color.nc_outcoming_text_default) + binding.messageQuote.quotedMessage.setTextColor(serverTheme.colorText) + binding.messageQuote.quotedMessageAuthor.setTextColor( + ColorUtils.setAlphaComponent( + serverTheme.colorText, + ALPHA_80_INT + ) ) - binding.messageQuote.quotedMessageAuthor.setTextColor(context!!.resources.getColor(R.color.nc_grey)) - binding.messageQuote.quoteColoredView.setBackgroundResource(R.color.white) + binding.messageQuote.quoteColoredView.setBackgroundColor(serverTheme.colorText) binding.messageQuote.quotedChatMessageView.visibility = View.VISIBLE } else { @@ -265,15 +288,16 @@ class OutcomingVoiceMessageViewHolder(outcomingView: View) : MessageHolders private fun colorizeMessageBubble(message: ChatMessage) { val resources = sharedApplication!!.resources + val elementColor = viewThemeUtils.getElementColor(binding.root.context) val bgBubbleColor = if (message.isDeleted) { - resources.getColor(R.color.bg_message_list_outcoming_bubble_deleted) + ColorUtils.setAlphaComponent(elementColor, HALF_ALPHA_INT) } else { - resources.getColor(R.color.bg_message_list_outcoming_bubble) + elementColor } if (message.isGrouped) { val bubbleDrawable = DisplayUtils.getMessageSelector( bgBubbleColor, - resources.getColor(R.color.transparent), + ResourcesCompat.getColor(resources, R.color.transparent, null), bgBubbleColor, R.drawable.shape_grouped_outcoming_message ) @@ -281,7 +305,7 @@ class OutcomingVoiceMessageViewHolder(outcomingView: View) : MessageHolders } else { val bubbleDrawable = DisplayUtils.getMessageSelector( bgBubbleColor, - resources.getColor(R.color.transparent), + ResourcesCompat.getColor(resources, R.color.transparent, null), bgBubbleColor, R.drawable.shape_outcoming_message ) @@ -300,5 +324,8 @@ class OutcomingVoiceMessageViewHolder(outcomingView: View) : MessageHolders companion object { private const val TAG = "VoiceOutMessageView" private const val SEEKBAR_START: Int = 0 + private const val HALF_ALPHA_INT: Int = 255 / 2 + private val ALPHA_80_INT: Int = (255 * 0.8).roundToInt() + private val ALPHA_60_INT: Int = (255 * 0.6).roundToInt() } } diff --git a/app/src/main/java/com/nextcloud/talk/application/NextcloudTalkApplication.kt b/app/src/main/java/com/nextcloud/talk/application/NextcloudTalkApplication.kt index c8e05df8f..5407a12f4 100644 --- a/app/src/main/java/com/nextcloud/talk/application/NextcloudTalkApplication.kt +++ b/app/src/main/java/com/nextcloud/talk/application/NextcloudTalkApplication.kt @@ -61,6 +61,7 @@ import com.nextcloud.talk.dagger.modules.ViewModelModule import com.nextcloud.talk.jobs.AccountRemovalWorker import com.nextcloud.talk.jobs.CapabilitiesWorker import com.nextcloud.talk.jobs.SignalingSettingsWorker +import com.nextcloud.talk.ui.theme.ThemeModule import com.nextcloud.talk.utils.ClosedInterfaceImpl import com.nextcloud.talk.utils.DeviceUtils import com.nextcloud.talk.utils.DisplayUtils @@ -96,7 +97,8 @@ import javax.inject.Singleton ArbitraryStorageModule::class, ViewModelModule::class, RepositoryModule::class, - UtilsModule::class + UtilsModule::class, + ThemeModule::class ] ) @Singleton @@ -120,6 +122,7 @@ class NextcloudTalkApplication : MultiDexApplication(), LifecycleObserver { override fun preKey(database: SQLiteDatabase) { // unused atm } + override fun postKey(database: SQLiteDatabase) { Log.i("TalkApplication", "DB cipher_migrate START") database.rawExecSQL("PRAGMA cipher_migrate;") diff --git a/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt b/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt index 170ddc447..e868911c4 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt +++ b/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt @@ -154,6 +154,8 @@ import com.nextcloud.talk.ui.dialog.MessageActionsDialog import com.nextcloud.talk.ui.dialog.ShowReactionsDialog import com.nextcloud.talk.ui.recyclerview.MessageSwipeActions import com.nextcloud.talk.ui.recyclerview.MessageSwipeCallback +import com.nextcloud.talk.ui.theme.ServerTheme +import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.AttendeePermissionsUtil import com.nextcloud.talk.utils.ConductorRemapping @@ -235,6 +237,12 @@ class ChatController(args: Bundle) : @Inject lateinit var permissionUtil: PlatformPermissionUtil + @Inject + lateinit var viewThemeUtils: ViewThemeUtils + + @Inject + lateinit var serverTheme: ServerTheme + val disposables = DisposableSet() var roomToken: String? = null @@ -872,6 +880,8 @@ class ChatController(args: Bundle) : .nc_description_send_message_button ) + viewThemeUtils.colorImageView(binding.messageInputView.button) + if (currentConversation != null && currentConversation?.roomId != null) { loadAvatarForStatusBar() setTitle() @@ -2675,7 +2685,8 @@ class ChatController(args: Bundle) : chatMessage, conversationUser, hasChatPermission, - ncApi!! + ncApi!!, + serverTheme ).show() } } diff --git a/app/src/main/java/com/nextcloud/talk/controllers/ContactsController.kt b/app/src/main/java/com/nextcloud/talk/controllers/ContactsController.kt index 8aea30693..04398483f 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/ContactsController.kt +++ b/app/src/main/java/com/nextcloud/talk/controllers/ContactsController.kt @@ -65,6 +65,7 @@ import com.nextcloud.talk.models.json.converters.EnumActorTypeConverter import com.nextcloud.talk.models.json.participants.Participant import com.nextcloud.talk.ui.dialog.ContactsBottomDialog import com.nextcloud.talk.users.UserManager +import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.ConductorRemapping import com.nextcloud.talk.utils.bundle.BundleKeys @@ -103,6 +104,9 @@ class ContactsController(args: Bundle) : @Inject lateinit var ncApi: NcApi + @Inject + lateinit var viewThemeUtils: ViewThemeUtils + private var credentials: String? = null private var currentUser: User? = null private var contactsQueryDisposable: Disposable? = null @@ -492,13 +496,14 @@ class ContactsController(args: Bundle) : val headerTitle = getHeaderTitle(participant) var genericTextHeaderItem: GenericTextHeaderItem if (!userHeaderItems.containsKey(headerTitle)) { - genericTextHeaderItem = GenericTextHeaderItem(headerTitle) + genericTextHeaderItem = GenericTextHeaderItem(headerTitle, viewThemeUtils) userHeaderItems.put(headerTitle, genericTextHeaderItem) } val newContactItem = ContactItem( participant, currentUser, - userHeaderItems[headerTitle] + userHeaderItems[headerTitle], + viewThemeUtils ) if (!contactItems!!.contains(newContactItem)) { newUserItemList.add(newContactItem) @@ -618,21 +623,16 @@ class ContactsController(args: Bundle) : binding.controllerGenericRv.recyclerView.setHasFixedSize(true) binding.controllerGenericRv.recyclerView.adapter = adapter binding.controllerGenericRv.swipeRefreshLayout.setOnRefreshListener { fetchData() } - binding.controllerGenericRv.swipeRefreshLayout.setColorSchemeResources(R.color.colorPrimary) - binding.controllerGenericRv.swipeRefreshLayout - .setProgressBackgroundColorSchemeResource(R.color.refresh_spinner_background) + + viewThemeUtils.themeSwipeRefreshLayout(binding.controllerGenericRv.swipeRefreshLayout) + binding.joinConversationViaLink.joinConversationViaLinkImageView .background .setColorFilter( ResourcesCompat.getColor(resources!!, R.color.colorBackgroundDarker, null), PorterDuff.Mode.SRC_IN ) - binding.conversationPrivacyToggle.publicCallLink - .background - .setColorFilter( - ResourcesCompat.getColor(resources!!, R.color.colorPrimary, null), - PorterDuff.Mode.SRC_IN - ) + viewThemeUtils.colorImageViewButton(binding.conversationPrivacyToggle.publicCallLink) disengageProgressBar() } diff --git a/app/src/main/java/com/nextcloud/talk/controllers/ConversationInfoController.kt b/app/src/main/java/com/nextcloud/talk/controllers/ConversationInfoController.kt index 866a5024f..6347acbb8 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/ConversationInfoController.kt +++ b/app/src/main/java/com/nextcloud/talk/controllers/ConversationInfoController.kt @@ -73,6 +73,7 @@ import com.nextcloud.talk.models.json.participants.Participant.ActorType.GROUPS import com.nextcloud.talk.models.json.participants.Participant.ActorType.USERS import com.nextcloud.talk.models.json.participants.ParticipantsOverall import com.nextcloud.talk.shareditems.activities.SharedItemsActivity +import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.DateConstants import com.nextcloud.talk.utils.DateUtils @@ -112,6 +113,9 @@ class ConversationInfoController(args: Bundle) : @Inject lateinit var eventBus: EventBus + @Inject + lateinit var viewThemeUtils: ViewThemeUtils + private val conversationToken: String? private val conversationUser: User? private val hasAvatarSpacing: Boolean @@ -181,6 +185,34 @@ class ConversationInfoController(args: Bundle) : } fetchRoomInfo() + + themeCategories() + themeSwitchPreferences() + } + + private fun themeSwitchPreferences() { + binding.run { + listOf( + binding.webinarInfoView.conversationInfoLobby, + binding.notificationSettingsView.callNotifications, + binding.notificationSettingsView.conversationInfoPriorityConversation + ).forEach(viewThemeUtils::colorSwitchPreference) + } + } + + private fun themeCategories() { + binding.run { + listOf( + conversationInfoName, + conversationDescription, + otherRoomOptions, + participantsListCategory, + ownOptions, + categorySharedItems, + binding.webinarInfoView.conversationInfoWebinar, + binding.notificationSettingsView.notificationSettingsCategory + ).forEach(viewThemeUtils::colorPreferenceCategory) + } } private fun showSharedItems() { @@ -299,7 +331,7 @@ class ConversationInfoController(args: Bundle) : val apiVersion = ApiUtils.getConversationApiVersion(conversationUser, intArrayOf(ApiUtils.APIv4, 1)) - ncApi?.setLobbyForConversation( + ncApi.setLobbyForConversation( ApiUtils.getCredentials(conversationUser!!.username, conversationUser.token), ApiUtils.getUrlForRoomWebinaryLobby(apiVersion, conversationUser.baseUrl, conversation!!.token), state, @@ -343,7 +375,7 @@ class ConversationInfoController(args: Bundle) : override fun onDetach(view: View) { super.onDetach(view) - eventBus?.unregister(this) + eventBus.unregister(this) } private fun showDeleteConversationDialog(savedInstanceState: Bundle?) { @@ -352,11 +384,11 @@ class ConversationInfoController(args: Bundle) : .setTopColorRes(R.color.nc_darkRed) .setIcon( DisplayUtils.getTintedDrawable( - context!!.resources, + context.resources, R.drawable.ic_delete_black_24dp, R.color.bg_default ) ) - .setPositiveButtonColor(context!!.resources.getColor(R.color.nc_darkRed)) + .setPositiveButtonColor(context.resources.getColor(R.color.nc_darkRed)) .setTitle(R.string.nc_delete_call) .setMessage(R.string.nc_delete_conversation_more) .setPositiveButton(R.string.nc_delete) { deleteConversation() } @@ -409,7 +441,7 @@ class ConversationInfoController(args: Bundle) : if (participant.sessionId != null) { userItem.isOnline = !participant.sessionId.equals("0") } else { - userItem.isOnline = !participant.sessionIds!!.isEmpty() + userItem.isOnline = !participant.sessionIds.isEmpty() } if (participant.calculatedActorType == USERS && @@ -453,7 +485,7 @@ class ConversationInfoController(args: Bundle) : val fieldMap = HashMap() fieldMap["includeStatus"] = true - ncApi?.getPeersForCall( + ncApi.getPeersForCall( credentials, ApiUtils.getUrlForParticipants( apiVersion, @@ -504,7 +536,7 @@ class ConversationInfoController(args: Bundle) : bundle.putStringArrayList(BundleKeys.KEY_EXISTING_PARTICIPANTS, existingParticipantsId) bundle.putString(BundleKeys.KEY_TOKEN, conversation!!.token) - getRouter().pushController( + router.pushController( ( RouterTransaction.with( ContactsController(bundle) @@ -537,11 +569,11 @@ class ConversationInfoController(args: Bundle) : .setTopColorRes(R.color.nc_darkRed) .setIcon( DisplayUtils.getTintedDrawable( - context!!.resources, + context.resources, R.drawable.ic_delete_black_24dp, R.color.bg_default ) ) - .setPositiveButtonColor(context!!.resources.getColor(R.color.nc_darkRed)) + .setPositiveButtonColor(context.resources.getColor(R.color.nc_darkRed)) .setTitle(R.string.nc_clear_history) .setMessage(R.string.nc_clear_history_warning) .setPositiveButton(R.string.nc_delete_all) { clearHistory() } @@ -555,7 +587,7 @@ class ConversationInfoController(args: Bundle) : private fun clearHistory() { val apiVersion = ApiUtils.getChatApiVersion(conversationUser, intArrayOf(1)) - ncApi?.clearChatHistory( + ncApi.clearChatHistory( credentials, ApiUtils.getUrlForChat(apiVersion, conversationUser!!.baseUrl, conversationToken) ) @@ -567,7 +599,7 @@ class ConversationInfoController(args: Bundle) : } override fun onNext(genericOverall: GenericOverall) { - Toast.makeText(context, context?.getString(R.string.nc_clear_history_success), Toast.LENGTH_LONG) + Toast.makeText(context, context.getString(R.string.nc_clear_history_success), Toast.LENGTH_LONG) .show() } @@ -606,7 +638,7 @@ class ConversationInfoController(args: Bundle) : apiVersion = ApiUtils.getConversationApiVersion(conversationUser, intArrayOf(ApiUtils.APIv4, 1)) } - ncApi?.getRoom(credentials, ApiUtils.getUrlForRoom(apiVersion, conversationUser!!.baseUrl, conversationToken)) + ncApi.getRoom(credentials, ApiUtils.getUrlForRoom(apiVersion, conversationUser!!.baseUrl, conversationToken)) ?.subscribeOn(Schedulers.io()) ?.observeOn(AndroidSchedulers.mainThread()) ?.subscribe(object : Observer { @@ -765,8 +797,8 @@ class ConversationInfoController(args: Bundle) : ) Conversation.ConversationType.ROOM_SYSTEM -> { val layers = arrayOfNulls(2) - layers[0] = ContextCompat.getDrawable(context!!, R.drawable.ic_launcher_background) - layers[1] = ContextCompat.getDrawable(context!!, R.drawable.ic_launcher_foreground) + layers[0] = ContextCompat.getDrawable(context, R.drawable.ic_launcher_background) + layers[1] = ContextCompat.getDrawable(context, R.drawable.ic_launcher_foreground) val layerDrawable = LayerDrawable(layers) binding.avatarImage.hierarchy.setPlaceholderImage(DisplayUtils.getRoundedDrawable(layerDrawable)) } @@ -800,7 +832,7 @@ class ConversationInfoController(args: Bundle) : if (participant.type == Participant.ParticipantType.MODERATOR || participant.type == Participant.ParticipantType.GUEST_MODERATOR ) { - ncApi?.demoteAttendeeFromModerator( + ncApi.demoteAttendeeFromModerator( credentials, ApiUtils.getUrlForRoomModerators( apiVersion, @@ -815,7 +847,7 @@ class ConversationInfoController(args: Bundle) : } else if (participant.type == Participant.ParticipantType.USER || participant.type == Participant.ParticipantType.GUEST ) { - ncApi?.promoteAttendeeToModerator( + ncApi.promoteAttendeeToModerator( credentials, ApiUtils.getUrlForRoomModerators( apiVersion, @@ -851,7 +883,7 @@ class ConversationInfoController(args: Bundle) : } if (participant.type == Participant.ParticipantType.MODERATOR) { - ncApi?.demoteModeratorToUser( + ncApi.demoteModeratorToUser( credentials, ApiUtils.getUrlForRoomModerators( apiVersion, @@ -864,7 +896,7 @@ class ConversationInfoController(args: Bundle) : ?.observeOn(AndroidSchedulers.mainThread()) ?.subscribe(subscriber) } else if (participant.type == Participant.ParticipantType.USER) { - ncApi?.promoteUserToModerator( + ncApi.promoteUserToModerator( credentials, ApiUtils.getUrlForRoomModerators( apiVersion, @@ -881,7 +913,7 @@ class ConversationInfoController(args: Bundle) : fun removeAttendeeFromConversation(apiVersion: Int, participant: Participant) { if (apiVersion >= ApiUtils.APIv4) { - ncApi?.removeAttendeeFromConversation( + ncApi.removeAttendeeFromConversation( credentials, ApiUtils.getUrlForAttendees( apiVersion, @@ -914,7 +946,7 @@ class ConversationInfoController(args: Bundle) : if (participant.type == Participant.ParticipantType.GUEST || participant.type == Participant.ParticipantType.USER_FOLLOWING_LINK ) { - ncApi?.removeParticipantFromConversation( + ncApi.removeParticipantFromConversation( credentials, ApiUtils.getUrlForRemovingParticipantFromConversation( conversationUser!!.baseUrl, @@ -944,7 +976,7 @@ class ConversationInfoController(args: Bundle) : } }) } else { - ncApi?.removeParticipantFromConversation( + ncApi.removeParticipantFromConversation( credentials, ApiUtils.getUrlForRemovingParticipantFromConversation( conversationUser!!.baseUrl, @@ -987,12 +1019,12 @@ class ConversationInfoController(args: Bundle) : val apiVersion = ApiUtils.getConversationApiVersion(conversationUser, intArrayOf(ApiUtils.APIv4, 1)) - if (participant.calculatedActorType == USERS && participant.calculatedActorId == conversationUser!!.userId) { + if (participant.calculatedActorType == USERS && participant.calculatedActorId == conversationUser.userId) { if (participant.attendeePin?.isNotEmpty() == true) { val items = mutableListOf( BasicListItemWithImage( R.drawable.ic_lock_grey600_24px, - context!!.getString(R.string.nc_attendee_pin, participant.attendeePin) + context.getString(R.string.nc_attendee_pin, participant.attendeePin) ) ) MaterialDialog(activity!!, BottomSheet(WRAP_CONTENT)).show { @@ -1018,7 +1050,7 @@ class ConversationInfoController(args: Bundle) : val items = mutableListOf( BasicListItemWithImage( R.drawable.ic_delete_grey600_24dp, - context!!.getString(R.string.nc_remove_group_and_members) + context.getString(R.string.nc_remove_group_and_members) ) ) MaterialDialog(activity!!, BottomSheet(WRAP_CONTENT)).show { @@ -1038,7 +1070,7 @@ class ConversationInfoController(args: Bundle) : val items = mutableListOf( BasicListItemWithImage( R.drawable.ic_delete_grey600_24dp, - context!!.getString(R.string.nc_remove_circle_and_members) + context.getString(R.string.nc_remove_circle_and_members) ) ) MaterialDialog(activity!!, BottomSheet(WRAP_CONTENT)).show { @@ -1057,19 +1089,19 @@ class ConversationInfoController(args: Bundle) : val items = mutableListOf( BasicListItemWithImage( R.drawable.ic_lock_grey600_24px, - context!!.getString(R.string.nc_attendee_pin, participant.attendeePin) + context.getString(R.string.nc_attendee_pin, participant.attendeePin) ), BasicListItemWithImage( R.drawable.ic_pencil_grey600_24dp, - context!!.getString(R.string.nc_promote) + context.getString(R.string.nc_promote) ), BasicListItemWithImage( R.drawable.ic_pencil_grey600_24dp, - context!!.getString(R.string.nc_demote) + context.getString(R.string.nc_demote) ), BasicListItemWithImage( R.drawable.ic_delete_grey600_24dp, - context!!.getString(R.string.nc_remove_participant) + context.getString(R.string.nc_remove_participant) ) ) @@ -1167,8 +1199,8 @@ class ConversationInfoController(args: Bundle) : return 1 } - return left.model.displayName!!.toLowerCase(Locale.ROOT).compareTo( - right.model.displayName!!.toLowerCase(Locale.ROOT) + return left.model.displayName!!.lowercase(Locale.ROOT).compareTo( + right.model.displayName!!.lowercase(Locale.ROOT) ) } } diff --git a/app/src/main/java/com/nextcloud/talk/controllers/ConversationsListController.java b/app/src/main/java/com/nextcloud/talk/controllers/ConversationsListController.java index a15883a29..72a953d8b 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/ConversationsListController.java +++ b/app/src/main/java/com/nextcloud/talk/controllers/ConversationsListController.java @@ -92,6 +92,7 @@ import com.nextcloud.talk.models.json.statuses.StatusesOverall; import com.nextcloud.talk.repositories.unifiedsearch.UnifiedSearchRepository; import com.nextcloud.talk.ui.dialog.ChooseAccountDialogFragment; import com.nextcloud.talk.ui.dialog.ConversationsListBottomDialog; +import com.nextcloud.talk.ui.theme.ViewThemeUtils; import com.nextcloud.talk.users.UserManager; import com.nextcloud.talk.utils.ApiUtils; import com.nextcloud.talk.utils.AttendeePermissionsUtil; @@ -181,6 +182,9 @@ public class ConversationsListController extends BaseController implements Flexi @Inject UnifiedSearchRepository unifiedSearchRepository; + @Inject + ViewThemeUtils viewThemeUtils; + @BindView(R.id.recycler_view) RecyclerView recyclerView; @@ -618,7 +622,7 @@ public class ConversationsListController extends BaseController implements Flexi GenericTextHeaderItem genericTextHeaderItem; if (!callHeaderItems.containsKey(headerTitle)) { - genericTextHeaderItem = new GenericTextHeaderItem(headerTitle); + genericTextHeaderItem = new GenericTextHeaderItem(headerTitle, viewThemeUtils); callHeaderItems.put(headerTitle, genericTextHeaderItem); } @@ -627,7 +631,8 @@ public class ConversationsListController extends BaseController implements Flexi conversation, currentUser, getActivity(), - userStatuses.get(conversation.getName())); + userStatuses.get(conversation.getName()), + viewThemeUtils); conversationItems.add(conversationItem); ConversationItem conversationItemWithHeader = new ConversationItem( @@ -635,7 +640,8 @@ public class ConversationsListController extends BaseController implements Flexi currentUser, getActivity(), callHeaderItems.get(headerTitle), - userStatuses.get(conversation.getName())); + userStatuses.get(conversation.getName()), + viewThemeUtils); conversationItemsWithHeader.add(conversationItemWithHeader); } } @@ -699,7 +705,7 @@ public class ConversationsListController extends BaseController implements Flexi GenericTextHeaderItem genericTextHeaderItem; if (!callHeaderItems.containsKey(headerTitle)) { - genericTextHeaderItem = new GenericTextHeaderItem(headerTitle); + genericTextHeaderItem = new GenericTextHeaderItem(headerTitle, viewThemeUtils); callHeaderItems.put(headerTitle, genericTextHeaderItem); } @@ -708,7 +714,8 @@ public class ConversationsListController extends BaseController implements Flexi currentUser, getActivity(), callHeaderItems.get(headerTitle), - userStatuses.get(conversation.getName())); + userStatuses.get(conversation.getName()), + viewThemeUtils); openConversationItems.add(conversationItem); } @@ -776,8 +783,7 @@ public class ConversationsListController extends BaseController implements Flexi }); swipeRefreshLayout.setOnRefreshListener(() -> fetchData()); - swipeRefreshLayout.setColorSchemeResources(R.color.colorPrimary); - swipeRefreshLayout.setProgressBackgroundColorSchemeResource(R.color.refresh_spinner_background); + viewThemeUtils.themeSwipeRefreshLayout(swipeRefreshLayout); emptyLayoutView.setOnClickListener(v -> showNewConversationsScreen()); floatingActionButton.setOnClickListener(v -> { @@ -785,6 +791,8 @@ public class ConversationsListController extends BaseController implements Flexi showNewConversationsScreen(); }); + viewThemeUtils.themeFAB(floatingActionButton); + if (getActivity() != null && getActivity() instanceof MainActivity) { MainActivity activity = (MainActivity) getActivity(); @@ -1409,7 +1417,7 @@ public class ConversationsListController extends BaseController implements Flexi List adapterItems = new ArrayList<>(entries.size() + 1); for (int i = 0; i < entries.size(); i++) { final boolean showHeader = i == 0; - adapterItems.add(new MessageResultItem(context, currentUser, entries.get(i), showHeader)); + adapterItems.add(new MessageResultItem(context, currentUser, entries.get(i), showHeader, viewThemeUtils)); } if (results.getHasMore()) { adapterItems.add(LoadMoreResultsItem.INSTANCE); diff --git a/app/src/main/java/com/nextcloud/talk/controllers/ProfileController.kt b/app/src/main/java/com/nextcloud/talk/controllers/ProfileController.kt index 52631f15f..add4ac128 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/ProfileController.kt +++ b/app/src/main/java/com/nextcloud/talk/controllers/ProfileController.kt @@ -24,10 +24,8 @@ package com.nextcloud.talk.controllers import android.app.Activity import android.content.Intent import android.content.pm.PackageManager -import android.content.res.ColorStateList import android.graphics.Bitmap import android.graphics.BitmapFactory -import android.graphics.Color import android.net.Uri import android.os.Bundle import android.text.Editable @@ -43,7 +41,6 @@ import android.view.ViewGroup import android.widget.Toast import androidx.annotation.ColorInt import androidx.annotation.DrawableRes -import androidx.core.content.ContextCompat import androidx.core.graphics.drawable.DrawableCompat import androidx.core.view.ViewCompat import androidx.recyclerview.widget.RecyclerView @@ -69,6 +66,7 @@ import com.nextcloud.talk.models.json.userprofile.UserProfileFieldsOverall import com.nextcloud.talk.models.json.userprofile.UserProfileOverall import com.nextcloud.talk.remotefilebrowser.activities.RemoteFileBrowserActivity import com.nextcloud.talk.ui.dialog.ScopeDialog +import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.users.UserManager import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.DisplayUtils @@ -110,6 +108,9 @@ class ProfileController : NewBaseController(R.layout.controller_profile) { @Inject lateinit var permissionUtil: PlatformPermissionUtil + @Inject + lateinit var viewThemeUtils: ViewThemeUtils + private var currentUser: User? = null private var edit = false private var adapter: UserInfoAdapter? = null @@ -196,7 +197,7 @@ class ProfileController : NewBaseController(R.layout.controller_profile) { override fun onAttach(view: View) { super.onAttach(view) - adapter = UserInfoAdapter(null, activity!!.resources.getColor(R.color.colorPrimary), this) + adapter = UserInfoAdapter(null, viewThemeUtils.getElementColor(activity!!), this) binding.userinfoList.adapter = adapter binding.userinfoList.setItemViewCacheSize(DEFAULT_CACHE_SIZE) currentUser = userManager.currentUser.blockingGet() @@ -260,6 +261,13 @@ class ProfileController : NewBaseController(R.layout.controller_profile) { // unused atm } }) + + colorIcons() + } + + private fun colorIcons() { + viewThemeUtils.colorImageView(binding.avatarChoose) + viewThemeUtils.colorImageView(binding.avatarCamera) } private fun isAllEmpty(items: Array): Boolean { @@ -301,7 +309,8 @@ class ProfileController : NewBaseController(R.layout.controller_profile) { binding.emptyList.root.visibility = View.VISIBLE setErrorMessageForMultiList( activity!!.getString(R.string.userinfo_no_info_headline), - activity!!.getString(R.string.userinfo_no_info_text), R.drawable.ic_user + activity!!.getString(R.string.userinfo_no_info_text), + R.drawable.ic_user ) } else { binding.emptyList.root.visibility = View.GONE @@ -616,11 +625,13 @@ class ProfileController : NewBaseController(R.layout.controller_profile) { val builder = MultipartBody.Builder() builder.setType(MultipartBody.FORM) builder.addFormDataPart( - "files[]", file!!.name, + "files[]", + file!!.name, file.asRequestBody(IMAGE_PREFIX_GENERIC.toMediaTypeOrNull()) ) val filePart: MultipartBody.Part = MultipartBody.Part.createFormData( - "files[]", file.name, + "files[]", + file.name, file.asRequestBody(IMAGE_JPG.toMediaTypeOrNull()) ) @@ -643,7 +654,8 @@ class ProfileController : NewBaseController(R.layout.controller_profile) { override fun onError(e: Throwable) { Toast.makeText( - applicationContext, context.getString(R.string.default_error_msg), + applicationContext, + context.getString(R.string.default_error_msg), Toast .LENGTH_LONG ).show() @@ -688,7 +700,8 @@ class ProfileController : NewBaseController(R.layout.controller_profile) { } class UserInfoDetailsItem( - @field:DrawableRes @param:DrawableRes var icon: Int, + @field:DrawableRes @param:DrawableRes + var icon: Int, var text: String?, var hint: String, val field: Field, @@ -748,22 +761,14 @@ class ProfileController : NewBaseController(R.layout.controller_profile) { DrawableCompat.setTint(holder.binding.icon.drawable, mTintColor) if (!TextUtils.isEmpty(item.text) || controller.edit) { holder.binding.userInfoDetailContainer.visibility = View.VISIBLE - if (controller.activity != null) { - holder.binding.userInfoEditText.setTextColor( - ContextCompat.getColor( - controller.activity!!, - R.color.conversation_item_header - ) - ) - } + controller.viewThemeUtils.colorTextInputLayout(holder.binding.userInfoInputLayout) if (controller.edit && controller.editableFields.contains(item.field.toString().lowercase()) ) { - holder.binding.userInfoEditText.isEnabled = true - holder.binding.userInfoEditText.isFocusableInTouchMode = true - holder.binding.userInfoEditText.isEnabled = true - holder.binding.userInfoEditText.isCursorVisible = true - holder.binding.userInfoEditText.backgroundTintList = ColorStateList.valueOf(mTintColor) + holder.binding.userInfoEditTextEdit.isEnabled = true + holder.binding.userInfoEditTextEdit.isFocusableInTouchMode = true + holder.binding.userInfoEditTextEdit.isEnabled = true + holder.binding.userInfoEditTextEdit.isCursorVisible = true holder.binding.scope.setOnClickListener { ScopeDialog( controller.activity!!, @@ -774,11 +779,10 @@ class ProfileController : NewBaseController(R.layout.controller_profile) { } holder.binding.scope.alpha = HIGH_EMPHASIS_ALPHA } else { - holder.binding.userInfoEditText.isEnabled = false - holder.binding.userInfoEditText.isFocusableInTouchMode = false - holder.binding.userInfoEditText.isEnabled = false - holder.binding.userInfoEditText.isCursorVisible = false - holder.binding.userInfoEditText.backgroundTintList = ColorStateList.valueOf(Color.TRANSPARENT) + holder.binding.userInfoEditTextEdit.isEnabled = false + holder.binding.userInfoEditTextEdit.isFocusableInTouchMode = false + holder.binding.userInfoEditTextEdit.isEnabled = false + holder.binding.userInfoEditTextEdit.isCursorVisible = false holder.binding.scope.setOnClickListener(null) holder.binding.scope.alpha = MEDIUM_EMPHASIS_ALPHA } @@ -791,19 +795,19 @@ class ProfileController : NewBaseController(R.layout.controller_profile) { holder: ViewHolder, item: UserInfoDetailsItem ) { - holder.binding.userInfoEditText.setText(item.text) - holder.binding.userInfoEditText.hint = item.hint - holder.binding.userInfoEditText.addTextChangedListener(object : TextWatcher { + holder.binding.userInfoEditTextEdit.setText(item.text) + holder.binding.userInfoInputLayout.hint = item.hint + holder.binding.userInfoEditTextEdit.addTextChangedListener(object : TextWatcher { override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) { // unused atm } override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { if (controller.edit) { - displayList!![holder.adapterPosition].text = holder.binding.userInfoEditText.text.toString() + displayList!![holder.adapterPosition].text = holder.binding.userInfoEditTextEdit.text.toString() } else { filteredDisplayList[holder.adapterPosition].text = - holder.binding.userInfoEditText.text.toString() + holder.binding.userInfoEditTextEdit.text.toString() } } diff --git a/app/src/main/java/com/nextcloud/talk/controllers/SettingsController.kt b/app/src/main/java/com/nextcloud/talk/controllers/SettingsController.kt index 98f7c5364..3ad36737b 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/SettingsController.kt +++ b/app/src/main/java/com/nextcloud/talk/controllers/SettingsController.kt @@ -78,6 +78,7 @@ import com.nextcloud.talk.jobs.ContactAddressBookWorker.Companion.deleteAll import com.nextcloud.talk.models.json.generic.GenericOverall import com.nextcloud.talk.models.json.userprofile.UserProfileOverall import com.nextcloud.talk.users.UserManager +import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.DisplayUtils import com.nextcloud.talk.utils.LoggingUtils.sendMailWithAttachment @@ -118,6 +119,9 @@ class SettingsController : NewBaseController(R.layout.controller_settings) { @Inject lateinit var currentUserProvider: CurrentUserProviderNew + @Inject + lateinit var viewThemeUtils: ViewThemeUtils + private var saveStateHandler: LovelySaveStateHandler? = null private var currentUser: User? = null private var credentials: String? = null @@ -402,7 +406,8 @@ class SettingsController : NewBaseController(R.layout.controller_settings) { .setIcon( DisplayUtils.getTintedDrawable( resources, - R.drawable.ic_delete_black_24dp, R.color.bg_default + R.drawable.ic_delete_black_24dp, + R.color.bg_default ) ) .setPositiveButtonColor(context!!.resources.getColor(R.color.nc_darkRed)) @@ -511,6 +516,34 @@ class SettingsController : NewBaseController(R.layout.controller_settings) { ) } + + themeCategories() + themeSwitchPreferences() + } + + private fun themeSwitchPreferences() { + binding.run { + listOf( + settingsScreenLock, + settingsScreenSecurity, + settingsIncognitoKeyboard, + settingsPhoneBookIntegration, + settingsReadPrivacy, + settingsProxyUseCredentials + ).forEach(viewThemeUtils::colorSwitchPreference) + } + } + + private fun themeCategories() { + binding.run { + listOf( + settingsNotificationsCategory, + settingsAboutCategory, + settingsAdvancedCategory, + settingsAppearanceCategory, + settingsPrivacyCategory + ).forEach(viewThemeUtils::colorPreferenceCategory) + } } private fun setupProxyTypeSettings() { @@ -952,7 +985,9 @@ class SettingsController : NewBaseController(R.layout.controller_settings) { val phoneNumber = textInputLayout.editText!!.text.toString() ncApi.setUserData( ApiUtils.getCredentials(currentUser!!.username, currentUser!!.token), - ApiUtils.getUrlForUserData(currentUser!!.baseUrl, currentUser!!.userId), "phone", phoneNumber + ApiUtils.getUrlForUserData(currentUser!!.baseUrl, currentUser!!.userId), + "phone", + phoneNumber ).subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(object : Observer { diff --git a/app/src/main/java/com/nextcloud/talk/controllers/bottomsheet/EntryMenuController.kt b/app/src/main/java/com/nextcloud/talk/controllers/bottomsheet/EntryMenuController.kt index 801ea39ac..c7e45e489 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/bottomsheet/EntryMenuController.kt +++ b/app/src/main/java/com/nextcloud/talk/controllers/bottomsheet/EntryMenuController.kt @@ -25,7 +25,7 @@ package com.nextcloud.talk.controllers.bottomsheet import android.content.ComponentName import android.content.Intent -import android.graphics.PorterDuff +import android.content.res.ColorStateList import android.os.Bundle import android.os.Parcelable import android.text.Editable @@ -34,6 +34,7 @@ import android.text.TextUtils import android.text.TextWatcher import android.view.View import android.view.inputmethod.EditorInfo +import androidx.core.content.res.ResourcesCompat import autodagger.AutoInjector import com.bluelinelabs.conductor.RouterTransaction import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler @@ -45,6 +46,8 @@ import com.nextcloud.talk.controllers.base.NewBaseController import com.nextcloud.talk.controllers.util.viewBinding import com.nextcloud.talk.databinding.ControllerEntryMenuBinding import com.nextcloud.talk.models.json.conversations.Conversation +import com.nextcloud.talk.ui.theme.ServerTheme +import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.users.UserManager import com.nextcloud.talk.utils.ShareUtils import com.nextcloud.talk.utils.UriUtils @@ -71,6 +74,12 @@ class EntryMenuController(args: Bundle) : @Inject lateinit var userManager: UserManager + @Inject + lateinit var viewThemeUtils: ViewThemeUtils + + @Inject + lateinit var serverTheme: ServerTheme + private val operation: ConversationOperationEnum private var conversation: Conversation? = null private var shareIntent: Intent? = null @@ -125,17 +134,11 @@ class EntryMenuController(args: Bundle) : rootView = view, editText = binding.textEdit, onEmojiPopupShownListener = { - if (resources != null) { - binding.smileyButton.setColorFilter( - resources!!.getColor(R.color.colorPrimary), - PorterDuff.Mode.SRC_IN - ) - } + viewThemeUtils.colorImageView(binding.smileyButton) }, onEmojiPopupDismissListener = { - binding.smileyButton.setColorFilter( - resources!!.getColor(R.color.emoji_icons), - PorterDuff.Mode.SRC_IN + binding.smileyButton.imageTintList = ColorStateList.valueOf( + ResourcesCompat.getColor(resources!!, R.color.medium_emphasis_text, context.theme) ) }, onEmojiClickListener = { @@ -171,6 +174,9 @@ class EntryMenuController(args: Bundle) : binding.textInputLayout.endIconMode = TextInputLayout.END_ICON_NONE } + viewThemeUtils.colorTextInputLayout(binding.textInputLayout) + viewThemeUtils.colorMaterialButtonText(binding.okButton) + binding.textInputLayout.hint = labelText binding.textInputLayout.requestFocus() diff --git a/app/src/main/java/com/nextcloud/talk/data/user/UsersDao.kt b/app/src/main/java/com/nextcloud/talk/data/user/UsersDao.kt index 16e72e20c..dc0cd86cc 100644 --- a/app/src/main/java/com/nextcloud/talk/data/user/UsersDao.kt +++ b/app/src/main/java/com/nextcloud/talk/data/user/UsersDao.kt @@ -32,6 +32,7 @@ import androidx.room.Transaction import androidx.room.Update import com.nextcloud.talk.data.user.model.UserEntity import io.reactivex.Maybe +import io.reactivex.Observable import io.reactivex.Single @Dao @@ -41,6 +42,10 @@ abstract class UsersDao { @Query("SELECT * FROM User where current = 1") abstract fun getActiveUser(): Maybe + // get active user + @Query("SELECT * FROM User where current = 1") + abstract fun getActiveUserObservable(): Observable + @Query("SELECT * FROM User where current = 1") abstract fun getActiveUserSynchronously(): UserEntity? diff --git a/app/src/main/java/com/nextcloud/talk/data/user/UsersRepository.kt b/app/src/main/java/com/nextcloud/talk/data/user/UsersRepository.kt index 624d8df2b..4d15a97b8 100644 --- a/app/src/main/java/com/nextcloud/talk/data/user/UsersRepository.kt +++ b/app/src/main/java/com/nextcloud/talk/data/user/UsersRepository.kt @@ -24,11 +24,13 @@ package com.nextcloud.talk.data.user import com.nextcloud.talk.data.user.model.User import io.reactivex.Maybe +import io.reactivex.Observable import io.reactivex.Single @Suppress("TooManyFunctions") interface UsersRepository { fun getActiveUser(): Maybe + fun getActiveUserObservable(): Observable fun getUsers(): Single> fun getUserWithId(id: Long): Maybe fun getUserWithIdNotScheduledForDeletion(id: Long): Maybe diff --git a/app/src/main/java/com/nextcloud/talk/data/user/UsersRepositoryImpl.kt b/app/src/main/java/com/nextcloud/talk/data/user/UsersRepositoryImpl.kt index 5dbda623c..d88d2147a 100644 --- a/app/src/main/java/com/nextcloud/talk/data/user/UsersRepositoryImpl.kt +++ b/app/src/main/java/com/nextcloud/talk/data/user/UsersRepositoryImpl.kt @@ -24,6 +24,7 @@ package com.nextcloud.talk.data.user import com.nextcloud.talk.data.user.model.User import io.reactivex.Maybe +import io.reactivex.Observable import io.reactivex.Single @Suppress("TooManyFunctions") @@ -33,6 +34,10 @@ class UsersRepositoryImpl(private val usersDao: UsersDao) : UsersRepository { return usersDao.getActiveUser().map { UserMapper.toModel(it) } } + override fun getActiveUserObservable(): Observable { + return usersDao.getActiveUserObservable().map { UserMapper.toModel(it) } + } + override fun getUsers(): Single> { return usersDao.getUsers().map { UserMapper.toModel(it) } } diff --git a/app/src/main/java/com/nextcloud/talk/messagesearch/MessageSearchActivity.kt b/app/src/main/java/com/nextcloud/talk/messagesearch/MessageSearchActivity.kt index 7c2883258..a9d553b25 100644 --- a/app/src/main/java/com/nextcloud/talk/messagesearch/MessageSearchActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/messagesearch/MessageSearchActivity.kt @@ -41,6 +41,7 @@ import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.controllers.ConversationsListController import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.databinding.ActivityMessageSearchBinding +import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.utils.DisplayUtils import com.nextcloud.talk.utils.bundle.BundleKeys import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew @@ -64,6 +65,9 @@ class MessageSearchActivity : BaseActivity() { @Inject lateinit var userProvider: CurrentUserProviderNew + @Inject + lateinit var viewThemeUtils: ViewThemeUtils + private lateinit var binding: ActivityMessageSearchBinding private lateinit var searchView: SearchView @@ -105,7 +109,9 @@ class MessageSearchActivity : BaseActivity() { DisplayUtils.applyColorToStatusBar( this, ResourcesCompat.getColor( - resources, R.color.appbar, null + resources, + R.color.appbar, + null ) ) DisplayUtils.applyColorToNavigationBar( @@ -154,7 +160,7 @@ class MessageSearchActivity : BaseActivity() { emptyList() } val newItems = - state.results.map { MessageResultItem(this, user, it) } + loadMoreItems + state.results.map { MessageResultItem(this, user, it, false, viewThemeUtils) } + loadMoreItems if (adapter != null) { adapter!!.updateDataSet(newItems) diff --git a/app/src/main/java/com/nextcloud/talk/models/json/capabilities/ThemingCapability.kt b/app/src/main/java/com/nextcloud/talk/models/json/capabilities/ThemingCapability.kt index 6dfdcae7b..83f4280c2 100644 --- a/app/src/main/java/com/nextcloud/talk/models/json/capabilities/ThemingCapability.kt +++ b/app/src/main/java/com/nextcloud/talk/models/json/capabilities/ThemingCapability.kt @@ -43,6 +43,10 @@ data class ThemingCapability( var colorText: String?, @JsonField(name = ["color-element"]) var colorElement: String?, + @JsonField(name = ["color-element-bright"]) + var colorElementBright: String?, + @JsonField(name = ["color-element-dark"]) + var colorElementDark: String?, @JsonField(name = ["logo"]) var logo: String?, @JsonField(name = ["background"]) @@ -53,5 +57,5 @@ data class ThemingCapability( var backgroundDefault: Boolean? ) : Parcelable { // This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject' - constructor() : this(null, null, null, null, null, null, null, null, null, null) + constructor() : this(null, null, null, null, null, null, null, null, null, null, null, null) } diff --git a/app/src/main/java/com/nextcloud/talk/polls/adapters/PollCreateOptionViewHolder.kt b/app/src/main/java/com/nextcloud/talk/polls/adapters/PollCreateOptionViewHolder.kt index 91afdd16c..8e3016fb4 100644 --- a/app/src/main/java/com/nextcloud/talk/polls/adapters/PollCreateOptionViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/polls/adapters/PollCreateOptionViewHolder.kt @@ -26,10 +26,12 @@ import android.text.TextWatcher import androidx.recyclerview.widget.RecyclerView import com.nextcloud.talk.R import com.nextcloud.talk.databinding.PollCreateOptionsItemBinding +import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.utils.EmojiTextInputEditText class PollCreateOptionViewHolder( - private val binding: PollCreateOptionsItemBinding + private val binding: PollCreateOptionsItemBinding, + private val viewThemeUtils: ViewThemeUtils ) : RecyclerView.ViewHolder(binding.root) { lateinit var optionText: EmojiTextInputEditText @@ -48,6 +50,7 @@ class PollCreateOptionViewHolder( } binding.pollOptionTextEdit.setText(pollCreateOptionItem.pollOption) + viewThemeUtils.colorEditText(binding.pollOptionTextEdit) if (focus) { itemsListener.requestFocus(binding.pollOptionTextEdit) diff --git a/app/src/main/java/com/nextcloud/talk/polls/adapters/PollCreateOptionsAdapter.kt b/app/src/main/java/com/nextcloud/talk/polls/adapters/PollCreateOptionsAdapter.kt index e827a56a6..724d87f40 100644 --- a/app/src/main/java/com/nextcloud/talk/polls/adapters/PollCreateOptionsAdapter.kt +++ b/app/src/main/java/com/nextcloud/talk/polls/adapters/PollCreateOptionsAdapter.kt @@ -24,9 +24,11 @@ import android.view.LayoutInflater import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView import com.nextcloud.talk.databinding.PollCreateOptionsItemBinding +import com.nextcloud.talk.ui.theme.ViewThemeUtils class PollCreateOptionsAdapter( - private val clickListener: PollCreateOptionsItemListener + private val clickListener: PollCreateOptionsItemListener, + private val viewThemeUtils: ViewThemeUtils ) : RecyclerView.Adapter() { internal var list: ArrayList = ArrayList() @@ -34,7 +36,7 @@ class PollCreateOptionsAdapter( override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PollCreateOptionViewHolder { val itemBinding = PollCreateOptionsItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) - return PollCreateOptionViewHolder(itemBinding) + return PollCreateOptionViewHolder(itemBinding, viewThemeUtils) } override fun onBindViewHolder(holder: PollCreateOptionViewHolder, position: Int) { diff --git a/app/src/main/java/com/nextcloud/talk/polls/adapters/PollResultHeaderViewHolder.kt b/app/src/main/java/com/nextcloud/talk/polls/adapters/PollResultHeaderViewHolder.kt index b963f515d..f08cb68cf 100644 --- a/app/src/main/java/com/nextcloud/talk/polls/adapters/PollResultHeaderViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/polls/adapters/PollResultHeaderViewHolder.kt @@ -23,15 +23,19 @@ package com.nextcloud.talk.polls.adapters import android.annotation.SuppressLint import android.graphics.Typeface import com.nextcloud.talk.databinding.PollResultHeaderItemBinding +import com.nextcloud.talk.ui.theme.ViewThemeUtils class PollResultHeaderViewHolder( - override val binding: PollResultHeaderItemBinding + override val binding: PollResultHeaderItemBinding, + private val viewThemeUtils: ViewThemeUtils ) : PollResultViewHolder(binding) { @SuppressLint("SetTextI18n") override fun bind(pollResultItem: PollResultItem, clickListener: PollResultItemClickListener) { val item = pollResultItem as PollResultHeaderItem + viewThemeUtils.colorProgressBar(binding.pollOptionBar) + binding.root.setOnClickListener { clickListener.onClick() } binding.pollOptionText.text = item.name diff --git a/app/src/main/java/com/nextcloud/talk/polls/adapters/PollResultsAdapter.kt b/app/src/main/java/com/nextcloud/talk/polls/adapters/PollResultsAdapter.kt index 98a576cba..df600fd37 100644 --- a/app/src/main/java/com/nextcloud/talk/polls/adapters/PollResultsAdapter.kt +++ b/app/src/main/java/com/nextcloud/talk/polls/adapters/PollResultsAdapter.kt @@ -27,10 +27,12 @@ import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.databinding.PollResultHeaderItemBinding import com.nextcloud.talk.databinding.PollResultVoterItemBinding import com.nextcloud.talk.databinding.PollResultVotersOverviewItemBinding +import com.nextcloud.talk.ui.theme.ViewThemeUtils class PollResultsAdapter( private val user: User, private val clickListener: PollResultItemClickListener, + private val viewThemeUtils: ViewThemeUtils ) : RecyclerView.Adapter() { internal var list: MutableList = ArrayList() @@ -43,7 +45,7 @@ class PollResultsAdapter( LayoutInflater.from(parent.context), parent, false ) - viewHolder = PollResultHeaderViewHolder(itemBinding) + viewHolder = PollResultHeaderViewHolder(itemBinding, viewThemeUtils) } PollResultVoterItem.VIEW_TYPE -> { val itemBinding = PollResultVoterItemBinding.inflate( diff --git a/app/src/main/java/com/nextcloud/talk/polls/ui/PollCreateDialogFragment.kt b/app/src/main/java/com/nextcloud/talk/polls/ui/PollCreateDialogFragment.kt index e94f1e750..1167ad128 100644 --- a/app/src/main/java/com/nextcloud/talk/polls/ui/PollCreateDialogFragment.kt +++ b/app/src/main/java/com/nextcloud/talk/polls/ui/PollCreateDialogFragment.kt @@ -43,6 +43,7 @@ import com.nextcloud.talk.polls.adapters.PollCreateOptionItem import com.nextcloud.talk.polls.adapters.PollCreateOptionsAdapter import com.nextcloud.talk.polls.adapters.PollCreateOptionsItemListener import com.nextcloud.talk.polls.viewmodels.PollCreateViewModel +import com.nextcloud.talk.ui.theme.ViewThemeUtils import javax.inject.Inject @AutoInjector(NextcloudTalkApplication::class) @@ -51,6 +52,9 @@ class PollCreateDialogFragment : DialogFragment(), PollCreateOptionsItemListener @Inject lateinit var viewModelFactory: ViewModelProvider.Factory + @Inject + lateinit var viewThemeUtils: ViewThemeUtils + private lateinit var binding: DialogPollCreateBinding private lateinit var viewModel: PollCreateViewModel @@ -85,13 +89,31 @@ class PollCreateDialogFragment : DialogFragment(), PollCreateOptionsItemListener binding.pollCreateOptionsList.layoutManager = LinearLayoutManager(context) - adapter = PollCreateOptionsAdapter(this) + adapter = PollCreateOptionsAdapter(this, viewThemeUtils) binding.pollCreateOptionsList.adapter = adapter + themeDialog() + setupListeners() setupStateObserver() } + private fun themeDialog() { + viewThemeUtils.colorTextViewText(binding.pollQuestion) + viewThemeUtils.colorTextViewText(binding.pollOptions) + viewThemeUtils.colorTextViewText(binding.pollSettings) + + viewThemeUtils.colorEditText(binding.pollCreateQuestionTextEdit) + + viewThemeUtils.colorMaterialButtonText(binding.pollAddOptionsItem) + // TODO button also needs a disabled state handling for colors + viewThemeUtils.colorMaterialButtonText(binding.pollDismiss) + viewThemeUtils.colorMaterialButtonBackground(binding.pollCreateButton) + + viewThemeUtils.themeCheckbox(binding.pollPrivatePollCheckbox) + viewThemeUtils.themeCheckbox(binding.pollMultipleAnswersCheckbox) + } + private fun setupListeners() { binding.pollAddOptionsItem.setOnClickListener { viewModel.addOption() diff --git a/app/src/main/java/com/nextcloud/talk/polls/ui/PollResultsFragment.kt b/app/src/main/java/com/nextcloud/talk/polls/ui/PollResultsFragment.kt index b1cbe4392..0a9c3db12 100644 --- a/app/src/main/java/com/nextcloud/talk/polls/ui/PollResultsFragment.kt +++ b/app/src/main/java/com/nextcloud/talk/polls/ui/PollResultsFragment.kt @@ -38,6 +38,7 @@ import com.nextcloud.talk.polls.adapters.PollResultItemClickListener import com.nextcloud.talk.polls.adapters.PollResultsAdapter import com.nextcloud.talk.polls.viewmodels.PollMainViewModel import com.nextcloud.talk.polls.viewmodels.PollResultsViewModel +import com.nextcloud.talk.ui.theme.ViewThemeUtils import javax.inject.Inject @AutoInjector(NextcloudTalkApplication::class) @@ -46,6 +47,9 @@ class PollResultsFragment : Fragment(), PollResultItemClickListener { @Inject lateinit var viewModelFactory: ViewModelProvider.Factory + @Inject + lateinit var viewThemeUtils: ViewThemeUtils + private lateinit var parentViewModel: PollMainViewModel lateinit var viewModel: PollResultsViewModel @@ -82,17 +86,24 @@ class PollResultsFragment : Fragment(), PollResultItemClickListener { } viewModel.items.observe(viewLifecycleOwner) { - val adapter = PollResultsAdapter(parentViewModel.user, this).apply { + val adapter = PollResultsAdapter(parentViewModel.user, this, viewThemeUtils).apply { if (it != null) { list = it } } binding.pollResultsList.adapter = adapter } + + themeDialog() + } + + private fun themeDialog() { + viewThemeUtils.colorMaterialButtonBackground(binding.editVoteButton) + viewThemeUtils.colorMaterialButtonText(binding.pollResultsEndPollButton) } private fun initAdapter() { - adapter = PollResultsAdapter(parentViewModel.user, this) + adapter = PollResultsAdapter(parentViewModel.user, this, viewThemeUtils) binding.pollResultsList.adapter = adapter binding.pollResultsList.layoutManager = LinearLayoutManager(context) } diff --git a/app/src/main/java/com/nextcloud/talk/polls/ui/PollVoteFragment.kt b/app/src/main/java/com/nextcloud/talk/polls/ui/PollVoteFragment.kt index 67b878442..5972334db 100644 --- a/app/src/main/java/com/nextcloud/talk/polls/ui/PollVoteFragment.kt +++ b/app/src/main/java/com/nextcloud/talk/polls/ui/PollVoteFragment.kt @@ -43,6 +43,7 @@ import com.nextcloud.talk.databinding.DialogPollVoteBinding import com.nextcloud.talk.polls.model.Poll import com.nextcloud.talk.polls.viewmodels.PollMainViewModel import com.nextcloud.talk.polls.viewmodels.PollVoteViewModel +import com.nextcloud.talk.ui.theme.ViewThemeUtils import javax.inject.Inject @AutoInjector(NextcloudTalkApplication::class) @@ -51,6 +52,9 @@ class PollVoteFragment : Fragment() { @Inject lateinit var viewModelFactory: ViewModelProvider.Factory + @Inject + lateinit var viewThemeUtils: ViewThemeUtils + private lateinit var parentViewModel: PollMainViewModel lateinit var viewModel: PollVoteViewModel @@ -117,6 +121,14 @@ class PollVoteFragment : Fragment() { binding.pollVoteEditDismiss.setOnClickListener { parentViewModel.dismissEditVotes() } + + themeDialog() + } + + private fun themeDialog() { + viewThemeUtils.colorMaterialButtonBackground(binding.pollVoteSubmitButton) + viewThemeUtils.colorMaterialButtonText(binding.pollVoteEndPollButton) + viewThemeUtils.colorMaterialButtonText(binding.pollVoteEditDismiss) } private fun updateDismissEditButton(showDismissEditButton: Boolean) { @@ -136,6 +148,7 @@ class PollVoteFragment : Fragment() { RadioButton(context).apply { text = option } }?.forEachIndexed { index, radioButton -> radioButton.id = index + viewThemeUtils.themeRadioButton(radioButton) makeOptionBoldIfSelfVoted(radioButton, poll, index) binding.pollVoteRadioGroup.addView(radioButton) @@ -156,6 +169,7 @@ class PollVoteFragment : Fragment() { setLayoutParams(layoutParams) } }?.forEachIndexed { index, checkBox -> + viewThemeUtils.themeCheckbox(checkBox) checkBox.id = index makeOptionBoldIfSelfVoted(checkBox, poll, index) binding.voteOptionsCheckboxesWrapper.addView(checkBox) diff --git a/app/src/main/java/com/nextcloud/talk/remotefilebrowser/activities/RemoteFileBrowserActivity.kt b/app/src/main/java/com/nextcloud/talk/remotefilebrowser/activities/RemoteFileBrowserActivity.kt index 75f87bf93..da0d78049 100644 --- a/app/src/main/java/com/nextcloud/talk/remotefilebrowser/activities/RemoteFileBrowserActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/remotefilebrowser/activities/RemoteFileBrowserActivity.kt @@ -44,6 +44,7 @@ import com.nextcloud.talk.remotefilebrowser.SelectionInterface import com.nextcloud.talk.remotefilebrowser.adapters.RemoteFileBrowserItemsAdapter import com.nextcloud.talk.remotefilebrowser.viewmodels.RemoteFileBrowserItemsViewModel import com.nextcloud.talk.ui.dialog.SortingOrderDialogFragment +import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.utils.DisplayUtils import com.nextcloud.talk.utils.FileSortOrder import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_MIME_TYPE_FILTER @@ -59,6 +60,9 @@ class RemoteFileBrowserActivity : AppCompatActivity(), SelectionInterface, Swipe @Inject lateinit var currentUserProvider: CurrentUserProviderNew + @Inject + lateinit var viewThemeUtils: ViewThemeUtils + private lateinit var binding: ActivityRemoteFileBrowserBinding private lateinit var viewModel: RemoteFileBrowserItemsViewModel @@ -91,8 +95,7 @@ class RemoteFileBrowserActivity : AppCompatActivity(), SelectionInterface, Swipe initViewModel(mimeTypeSelectionFilter) binding.swipeRefreshList.setOnRefreshListener(this) - binding.swipeRefreshList.setColorSchemeResources(R.color.colorPrimary) - binding.swipeRefreshList.setProgressBackgroundColorSchemeResource(R.color.refresh_spinner_background) + viewThemeUtils.themeSwipeRefreshLayout(binding.swipeRefreshList) binding.pathNavigationBackButton.setOnClickListener { viewModel.navigateUp() } binding.sortButton.setOnClickListener { changeSorting() } @@ -160,6 +163,7 @@ class RemoteFileBrowserActivity : AppCompatActivity(), SelectionInterface, Swipe mimeTypeSelectionFilter = mimeTypeSelectionFilter, user = currentUserProvider.currentUser.blockingGet(), selectionInterface = this, + viewThemeUtils = viewThemeUtils, onItemClicked = viewModel::onItemClicked ) adapter.items = remoteFileBrowserItems diff --git a/app/src/main/java/com/nextcloud/talk/remotefilebrowser/adapters/RemoteFileBrowserItemsAdapter.kt b/app/src/main/java/com/nextcloud/talk/remotefilebrowser/adapters/RemoteFileBrowserItemsAdapter.kt index 5a146cb11..d63da5449 100644 --- a/app/src/main/java/com/nextcloud/talk/remotefilebrowser/adapters/RemoteFileBrowserItemsAdapter.kt +++ b/app/src/main/java/com/nextcloud/talk/remotefilebrowser/adapters/RemoteFileBrowserItemsAdapter.kt @@ -28,19 +28,20 @@ import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.databinding.RvItemBrowserFileBinding import com.nextcloud.talk.remotefilebrowser.SelectionInterface import com.nextcloud.talk.remotefilebrowser.model.RemoteFileBrowserItem +import com.nextcloud.talk.ui.theme.ViewThemeUtils class RemoteFileBrowserItemsAdapter( private val showGrid: Boolean = false, private val mimeTypeSelectionFilter: String? = null, private val user: User, private val selectionInterface: SelectionInterface, + private val viewThemeUtils: ViewThemeUtils, private val onItemClicked: (RemoteFileBrowserItem) -> Unit ) : RecyclerView.Adapter() { var items: List = emptyList() override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RemoteFileBrowserItemsViewHolder { - return if (showGrid) { RemoteFileBrowserItemsListViewHolder( RvItemBrowserFileBinding.inflate( @@ -50,7 +51,8 @@ class RemoteFileBrowserItemsAdapter( ), mimeTypeSelectionFilter, user, - selectionInterface + selectionInterface, + viewThemeUtils ) { onItemClicked(items[it]) } @@ -63,7 +65,8 @@ class RemoteFileBrowserItemsAdapter( ), mimeTypeSelectionFilter, user, - selectionInterface + selectionInterface, + viewThemeUtils ) { onItemClicked(items[it]) } diff --git a/app/src/main/java/com/nextcloud/talk/remotefilebrowser/adapters/RemoteFileBrowserItemsListViewHolder.kt b/app/src/main/java/com/nextcloud/talk/remotefilebrowser/adapters/RemoteFileBrowserItemsListViewHolder.kt index 22ad32704..241707a91 100644 --- a/app/src/main/java/com/nextcloud/talk/remotefilebrowser/adapters/RemoteFileBrowserItemsListViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/remotefilebrowser/adapters/RemoteFileBrowserItemsListViewHolder.kt @@ -22,7 +22,6 @@ package com.nextcloud.talk.remotefilebrowser.adapters import android.text.format.Formatter import android.view.View -import androidx.appcompat.content.res.AppCompatResources import autodagger.AutoInjector import com.facebook.drawee.backends.pipeline.Fresco import com.facebook.drawee.interfaces.DraweeController @@ -33,10 +32,10 @@ import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.databinding.RvItemBrowserFileBinding import com.nextcloud.talk.remotefilebrowser.SelectionInterface import com.nextcloud.talk.remotefilebrowser.model.RemoteFileBrowserItem +import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.DateUtils.getLocalDateTimeStringFromTimestamp import com.nextcloud.talk.utils.DisplayUtils -import com.nextcloud.talk.utils.DrawableUtils.getDrawableResourceIdForMimeType import com.nextcloud.talk.utils.Mimetype.FOLDER @AutoInjector(NextcloudTalkApplication::class) @@ -45,6 +44,7 @@ class RemoteFileBrowserItemsListViewHolder( mimeTypeSelectionFilter: String?, currentUser: User, selectionInterface: SelectionInterface, + private val viewThemeUtils: ViewThemeUtils, onItemClicked: (Int) -> Unit ) : RemoteFileBrowserItemsViewHolder(binding, mimeTypeSelectionFilter, currentUser, selectionInterface) { @@ -66,7 +66,6 @@ class RemoteFileBrowserItemsListViewHolder( } override fun onBind(item: RemoteFileBrowserItem) { - super.onBind(item) binding.fileIcon.controller = null @@ -99,9 +98,7 @@ class RemoteFileBrowserItemsListViewHolder( binding.fileIcon .hierarchy .setPlaceholderImage( - AppCompatResources.getDrawable( - binding.fileIcon.context, getDrawableResourceIdForMimeType(item.mimeType) - ) + viewThemeUtils.getPlaceholderImage(binding.root.context, item.mimeType) ) if (item.hasPreview) { @@ -132,6 +129,7 @@ class RemoteFileBrowserItemsListViewHolder( private fun setSelectability() { if (selectable) { binding.selectFileCheckbox.visibility = View.VISIBLE + viewThemeUtils.themeCheckbox(binding.selectFileCheckbox) } else { binding.selectFileCheckbox.visibility = View.GONE } diff --git a/app/src/main/java/com/nextcloud/talk/shareditems/activities/SharedItemsActivity.kt b/app/src/main/java/com/nextcloud/talk/shareditems/activities/SharedItemsActivity.kt index 2689e0829..48b415c59 100644 --- a/app/src/main/java/com/nextcloud/talk/shareditems/activities/SharedItemsActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/shareditems/activities/SharedItemsActivity.kt @@ -41,6 +41,7 @@ import com.nextcloud.talk.databinding.ActivitySharedItemsBinding import com.nextcloud.talk.shareditems.adapters.SharedItemsAdapter import com.nextcloud.talk.shareditems.model.SharedItemType import com.nextcloud.talk.shareditems.viewmodels.SharedItemsViewModel +import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.utils.DisplayUtils import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_CONVERSATION_NAME import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN @@ -53,6 +54,9 @@ class SharedItemsActivity : AppCompatActivity() { @Inject lateinit var viewModelFactory: ViewModelProvider.Factory + @Inject + lateinit var viewThemeUtils: ViewThemeUtils + private lateinit var binding: ActivitySharedItemsBinding private lateinit var viewModel: SharedItemsViewModel @@ -72,7 +76,9 @@ class SharedItemsActivity : AppCompatActivity() { DisplayUtils.applyColorToStatusBar( this, ResourcesCompat.getColor( - resources, R.color.appbar, null + resources, + R.color.appbar, + null ) ) DisplayUtils.applyColorToNavigationBar( @@ -130,7 +136,8 @@ class SharedItemsActivity : AppCompatActivity() { showGrid, user, roomToken, - isUserConversationOwnerOrModerator + isUserConversationOwnerOrModerator, + viewThemeUtils ).apply { items = sharedMediaItems.items } @@ -142,6 +149,8 @@ class SharedItemsActivity : AppCompatActivity() { } else -> {} } + + viewThemeUtils.colorTabLayout(binding.sharedItemsTabs) } private fun clearEmptyLoading() { @@ -161,7 +170,6 @@ class SharedItemsActivity : AppCompatActivity() { } private fun initTabs(sharedItemTypes: Set) { - binding.sharedItemsTabs.removeAllTabs() if (sharedItemTypes.contains(SharedItemType.MEDIA)) { diff --git a/app/src/main/java/com/nextcloud/talk/shareditems/adapters/SharedItemsAdapter.kt b/app/src/main/java/com/nextcloud/talk/shareditems/adapters/SharedItemsAdapter.kt index ac98b91b1..5c2719e9d 100644 --- a/app/src/main/java/com/nextcloud/talk/shareditems/adapters/SharedItemsAdapter.kt +++ b/app/src/main/java/com/nextcloud/talk/shareditems/adapters/SharedItemsAdapter.kt @@ -37,18 +37,19 @@ import com.nextcloud.talk.shareditems.model.SharedItem import com.nextcloud.talk.shareditems.model.SharedLocationItem import com.nextcloud.talk.shareditems.model.SharedOtherItem import com.nextcloud.talk.shareditems.model.SharedPollItem +import com.nextcloud.talk.ui.theme.ViewThemeUtils class SharedItemsAdapter( private val showGrid: Boolean, private val user: User, private val roomToken: String, - private val isUserConversationOwnerOrModerator: Boolean + private val isUserConversationOwnerOrModerator: Boolean, + private val viewThemeUtils: ViewThemeUtils ) : RecyclerView.Adapter() { var items: List = emptyList() override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SharedItemsViewHolder { - return if (showGrid) { SharedItemsGridViewHolder( SharedItemGridBinding.inflate( @@ -56,7 +57,8 @@ class SharedItemsAdapter( parent, false ), - user + user, + viewThemeUtils ) } else { SharedItemsListViewHolder( @@ -65,7 +67,8 @@ class SharedItemsAdapter( parent, false ), - user + user, + viewThemeUtils ) } } diff --git a/app/src/main/java/com/nextcloud/talk/shareditems/adapters/SharedItemsGridViewHolder.kt b/app/src/main/java/com/nextcloud/talk/shareditems/adapters/SharedItemsGridViewHolder.kt index 4e672754e..30977e6c8 100644 --- a/app/src/main/java/com/nextcloud/talk/shareditems/adapters/SharedItemsGridViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/shareditems/adapters/SharedItemsGridViewHolder.kt @@ -27,11 +27,13 @@ import android.widget.ProgressBar import com.facebook.drawee.view.SimpleDraweeView import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.databinding.SharedItemGridBinding +import com.nextcloud.talk.ui.theme.ViewThemeUtils class SharedItemsGridViewHolder( override val binding: SharedItemGridBinding, - user: User -) : SharedItemsViewHolder(binding, user) { + user: User, + viewThemeUtils: ViewThemeUtils +) : SharedItemsViewHolder(binding, user, viewThemeUtils) { override val image: SimpleDraweeView get() = binding.image diff --git a/app/src/main/java/com/nextcloud/talk/shareditems/adapters/SharedItemsListViewHolder.kt b/app/src/main/java/com/nextcloud/talk/shareditems/adapters/SharedItemsListViewHolder.kt index fa611d839..f63549ede 100644 --- a/app/src/main/java/com/nextcloud/talk/shareditems/adapters/SharedItemsListViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/shareditems/adapters/SharedItemsListViewHolder.kt @@ -38,11 +38,13 @@ import com.nextcloud.talk.shareditems.model.SharedItem import com.nextcloud.talk.shareditems.model.SharedLocationItem import com.nextcloud.talk.shareditems.model.SharedOtherItem import com.nextcloud.talk.shareditems.model.SharedPollItem +import com.nextcloud.talk.ui.theme.ViewThemeUtils class SharedItemsListViewHolder( override val binding: SharedItemListBinding, - user: User -) : SharedItemsViewHolder(binding, user) { + user: User, + viewThemeUtils: ViewThemeUtils +) : SharedItemsViewHolder(binding, user, viewThemeUtils) { override val image: SimpleDraweeView get() = binding.fileImage @@ -52,7 +54,6 @@ class SharedItemsListViewHolder( get() = binding.progressBar override fun onBind(item: SharedFileItem) { - super.onBind(item) binding.fileName.text = item.name diff --git a/app/src/main/java/com/nextcloud/talk/shareditems/adapters/SharedItemsViewHolder.kt b/app/src/main/java/com/nextcloud/talk/shareditems/adapters/SharedItemsViewHolder.kt index 205bbcb7c..cad4743dd 100644 --- a/app/src/main/java/com/nextcloud/talk/shareditems/adapters/SharedItemsViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/shareditems/adapters/SharedItemsViewHolder.kt @@ -23,12 +23,10 @@ package com.nextcloud.talk.shareditems.adapters import android.content.Context -import android.graphics.drawable.Drawable import android.net.Uri import android.util.Log import android.view.View import android.widget.ProgressBar -import androidx.core.content.ContextCompat import androidx.recyclerview.widget.RecyclerView import androidx.viewbinding.ViewBinding import com.facebook.drawee.backends.pipeline.Fresco @@ -43,16 +41,17 @@ import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.shareditems.model.SharedDeckCardItem import com.nextcloud.talk.shareditems.model.SharedFileItem import com.nextcloud.talk.shareditems.model.SharedItem +import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.shareditems.model.SharedLocationItem import com.nextcloud.talk.shareditems.model.SharedOtherItem import com.nextcloud.talk.shareditems.model.SharedPollItem import com.nextcloud.talk.utils.ApiUtils -import com.nextcloud.talk.utils.DrawableUtils import com.nextcloud.talk.utils.FileViewerUtils abstract class SharedItemsViewHolder( open val binding: ViewBinding, - internal val user: User + internal val user: User, + private val viewThemeUtils: ViewThemeUtils ) : RecyclerView.ViewHolder(binding.root) { companion object { @@ -71,7 +70,7 @@ abstract class SharedItemsViewHolder( ) open fun onBind(item: SharedFileItem) { - image.hierarchy.setPlaceholderImage(staticImage(item.mimeType, image)) + image.hierarchy.setPlaceholderImage(viewThemeUtils.getPlaceholderImage(image.context, item.mimeType)) if (item.previewAvailable) { image.controller = configurePreview(item) } @@ -107,7 +106,6 @@ abstract class SharedItemsViewHolder( } private fun configurePreview(item: SharedFileItem): DraweeController { - val imageRequest = ImageRequestBuilder.newBuilderWithSource(Uri.parse(item.previewLink)) .setProgressiveRenderingEnabled(true) .setRotationOptions(RotationOptions.autoRotate()) @@ -136,12 +134,4 @@ abstract class SharedItemsViewHolder( open fun onBind(item: SharedOtherItem) {} open fun onBind(item: SharedDeckCardItem) {} - - private fun staticImage( - mimeType: String?, - image: SimpleDraweeView - ): Drawable { - val drawableResourceId = DrawableUtils.getDrawableResourceIdForMimeType(mimeType) - return ContextCompat.getDrawable(image.context, drawableResourceId)!! - } } diff --git a/app/src/main/java/com/nextcloud/talk/ui/dialog/AudioOutputDialog.kt b/app/src/main/java/com/nextcloud/talk/ui/dialog/AudioOutputDialog.kt index e4f9105f4..5e879e736 100644 --- a/app/src/main/java/com/nextcloud/talk/ui/dialog/AudioOutputDialog.kt +++ b/app/src/main/java/com/nextcloud/talk/ui/dialog/AudioOutputDialog.kt @@ -24,18 +24,33 @@ import android.os.Bundle import android.util.Log import android.view.View import android.view.ViewGroup -import androidx.core.content.ContextCompat +import autodagger.AutoInjector import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog import com.nextcloud.talk.R import com.nextcloud.talk.activities.CallActivity +import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.databinding.DialogAudioOutputBinding +import com.nextcloud.talk.ui.theme.ServerTheme +import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.webrtc.WebRtcAudioManager +import javax.inject.Inject +@AutoInjector(NextcloudTalkApplication::class) class AudioOutputDialog(val callActivity: CallActivity) : BottomSheetDialog(callActivity) { + @Inject + lateinit var viewThemeUtils: ViewThemeUtils + + @Inject + lateinit var serverTheme: ServerTheme + private lateinit var dialogAudioOutputBinding: DialogAudioOutputBinding + init { + NextcloudTalkApplication.sharedApplication?.componentApplication?.inject(this) + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) dialogAudioOutputBinding = DialogAudioOutputBinding.inflate(layoutInflater) @@ -82,55 +97,23 @@ class AudioOutputDialog(val callActivity: CallActivity) : BottomSheetDialog(call private fun highlightActiveOutputChannel() { when (callActivity.audioManager?.currentAudioDevice) { WebRtcAudioManager.AudioDevice.BLUETOOTH -> { - dialogAudioOutputBinding.audioOutputBluetoothIcon.setColorFilter( - ContextCompat.getColor( - context, - R.color.colorPrimary - ), - android.graphics.PorterDuff.Mode.SRC_IN - ) - dialogAudioOutputBinding.audioOutputBluetoothText.setTextColor( - callActivity.resources.getColor(R.color.colorPrimary) - ) + viewThemeUtils.colorImageView(dialogAudioOutputBinding.audioOutputBluetoothIcon) + dialogAudioOutputBinding.audioOutputBluetoothText.setTextColor(serverTheme.primaryColor) } WebRtcAudioManager.AudioDevice.SPEAKER_PHONE -> { - dialogAudioOutputBinding.audioOutputSpeakerIcon.setColorFilter( - ContextCompat.getColor( - context, - R.color.colorPrimary - ), - android.graphics.PorterDuff.Mode.SRC_IN - ) - dialogAudioOutputBinding.audioOutputSpeakerText.setTextColor( - callActivity.resources.getColor(R.color.colorPrimary) - ) + viewThemeUtils.colorImageView(dialogAudioOutputBinding.audioOutputSpeakerIcon) + dialogAudioOutputBinding.audioOutputSpeakerText.setTextColor(serverTheme.primaryColor) } WebRtcAudioManager.AudioDevice.EARPIECE -> { - dialogAudioOutputBinding.audioOutputEarspeakerIcon.setColorFilter( - ContextCompat.getColor( - context, - R.color.colorPrimary - ), - android.graphics.PorterDuff.Mode.SRC_IN - ) - dialogAudioOutputBinding.audioOutputEarspeakerText.setTextColor( - callActivity.resources.getColor(R.color.colorPrimary) - ) + viewThemeUtils.colorImageView(dialogAudioOutputBinding.audioOutputEarspeakerIcon) + dialogAudioOutputBinding.audioOutputEarspeakerText.setTextColor(serverTheme.primaryColor) } WebRtcAudioManager.AudioDevice.WIRED_HEADSET -> { - dialogAudioOutputBinding.audioOutputWiredHeadsetIcon.setColorFilter( - ContextCompat.getColor( - context, - R.color.colorPrimary - ), - android.graphics.PorterDuff.Mode.SRC_IN - ) - dialogAudioOutputBinding.audioOutputWiredHeadsetText.setTextColor( - callActivity.resources.getColor(R.color.colorPrimary) - ) + viewThemeUtils.colorImageView(dialogAudioOutputBinding.audioOutputWiredHeadsetIcon) + dialogAudioOutputBinding.audioOutputWiredHeadsetText.setTextColor(serverTheme.primaryColor) } else -> Log.d(TAG, "AudioOutputDialog doesn't know this AudioDevice") diff --git a/app/src/main/java/com/nextcloud/talk/ui/dialog/ChooseAccountDialogFragment.java b/app/src/main/java/com/nextcloud/talk/ui/dialog/ChooseAccountDialogFragment.java index c78db0e4c..3881bed5c 100644 --- a/app/src/main/java/com/nextcloud/talk/ui/dialog/ChooseAccountDialogFragment.java +++ b/app/src/main/java/com/nextcloud/talk/ui/dialog/ChooseAccountDialogFragment.java @@ -51,6 +51,7 @@ import com.nextcloud.talk.models.json.status.Status; import com.nextcloud.talk.models.json.status.StatusOverall; import com.nextcloud.talk.ui.StatusDrawable; import com.nextcloud.talk.users.UserManager; +import com.nextcloud.talk.ui.theme.ViewThemeUtils; import com.nextcloud.talk.utils.ApiUtils; import com.nextcloud.talk.utils.DisplayUtils; import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew; @@ -87,6 +88,9 @@ public class ChooseAccountDialogFragment extends DialogFragment { @Inject NcApi ncApi; + @Inject + ViewThemeUtils viewThemeUtils; + private DialogChooseAccountBinding binding; private View dialogView; @@ -120,6 +124,9 @@ public class ChooseAccountDialogFragment extends DialogFragment { binding.currentAccount.ticker.setVisibility(View.GONE); binding.currentAccount.account.setText((Uri.parse(user.getBaseUrl()).getHost())); + viewThemeUtils.colorImageView(binding.currentAccount.accountMenu); + + if (user.getBaseUrl() != null && (user.getBaseUrl().startsWith("http://") || user.getBaseUrl().startsWith("https://"))) { binding.currentAccount.userIcon.setVisibility(View.VISIBLE); diff --git a/app/src/main/java/com/nextcloud/talk/ui/dialog/SetStatusDialogFragment.kt b/app/src/main/java/com/nextcloud/talk/ui/dialog/SetStatusDialogFragment.kt index e0d8630f9..6c6dfb9d6 100644 --- a/app/src/main/java/com/nextcloud/talk/ui/dialog/SetStatusDialogFragment.kt +++ b/app/src/main/java/com/nextcloud/talk/ui/dialog/SetStatusDialogFragment.kt @@ -34,12 +34,15 @@ import android.view.inputmethod.InputMethodManager import android.widget.AdapterView import android.widget.AdapterView.OnItemSelectedListener import android.widget.ArrayAdapter +import android.widget.ImageView +import android.widget.TextView import androidx.appcompat.app.AlertDialog import androidx.core.widget.doAfterTextChanged import androidx.fragment.app.DialogFragment import androidx.recyclerview.widget.LinearLayoutManager import autodagger.AutoInjector import com.bluelinelabs.logansquare.LoganSquare +import com.google.android.material.card.MaterialCardView import com.nextcloud.talk.R import com.nextcloud.talk.adapters.PredefinedStatusClickListener import com.nextcloud.talk.adapters.PredefinedStatusListAdapter @@ -53,6 +56,7 @@ import com.nextcloud.talk.models.json.status.Status import com.nextcloud.talk.models.json.status.StatusType import com.nextcloud.talk.models.json.status.predefined.PredefinedStatus import com.nextcloud.talk.models.json.status.predefined.PredefinedStatusOverall +import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.DisplayUtils import com.vanniktech.emoji.EmojiPopup @@ -105,6 +109,9 @@ class SetStatusDialogFragment : @Inject lateinit var ncApi: NcApi + @Inject + lateinit var viewThemeUtils: ViewThemeUtils + lateinit var credentials: String override fun onCreate(savedInstanceState: Bundle?) { @@ -234,8 +241,8 @@ class SetStatusDialogFragment : } } - binding.clearStatus.setTextColor(resources.getColor(R.color.colorPrimary)) - binding.setStatus.setBackgroundColor(resources.getColor(R.color.colorPrimary)) + viewThemeUtils.colorMaterialButtonText(binding.clearStatus) + viewThemeUtils.colorMaterialButtonBackground(binding.setStatus) binding.customStatusInput.highlightColor = resources.getColor(R.color.colorPrimary) @@ -258,7 +265,6 @@ class SetStatusDialogFragment : @Suppress("ComplexMethod") private fun setClearStatusAfterValue(item: Int) { - val currentTime = System.currentTimeMillis() / ONE_SECOND_IN_MILLIS when (item) { @@ -310,7 +316,6 @@ class SetStatusDialogFragment : } private fun clearAtToUnixTime(clearAt: ClearAt?): Long { - var returnValue = -1L if (clearAt != null) { @@ -400,25 +405,18 @@ class SetStatusDialogFragment : private fun visualizeStatus(statusType: StatusType) { clearTopStatus() - when (statusType) { - StatusType.ONLINE -> { - binding.onlineStatus.setCardBackgroundColor(resources.getColor(R.color.colorPrimary)) - binding.onlineHeadline.setTextColor(resources.getColor(R.color.high_emphasis_text_dark_background)) + val views: Triple = when (statusType) { + StatusType.ONLINE -> Triple(binding.onlineStatus, binding.onlineHeadline, binding.onlineIcon) + StatusType.AWAY -> Triple(binding.awayStatus, binding.awayHeadline, binding.awayIcon) + StatusType.DND -> Triple(binding.dndStatus, binding.dndHeadline, binding.dndIcon) + StatusType.INVISIBLE -> Triple(binding.invisibleStatus, binding.invisibleHeadline, binding.invisibleIcon) + else -> { + Log.d(TAG, "unknown status") + return } - StatusType.AWAY -> { - binding.awayStatus.setCardBackgroundColor(resources.getColor(R.color.colorPrimary)) - binding.awayHeadline.setTextColor(resources.getColor(R.color.high_emphasis_text_dark_background)) - } - StatusType.DND -> { - binding.dndStatus.setCardBackgroundColor(resources.getColor(R.color.colorPrimary)) - binding.dndHeadline.setTextColor(resources.getColor(R.color.high_emphasis_text_dark_background)) - } - StatusType.INVISIBLE -> { - binding.invisibleStatus.setCardBackgroundColor(resources.getColor(R.color.colorPrimary)) - binding.invisibleHeadline.setTextColor(resources.getColor(R.color.high_emphasis_text_dark_background)) - } - else -> Log.d(TAG, "unknown status") } + viewThemeUtils.colorCardViewBackground(views.first) + viewThemeUtils.colorTextViewText(views.second) } private fun clearTopStatus() { @@ -433,11 +431,15 @@ class SetStatusDialogFragment : binding.awayHeadline.setTextColor(resources.getColor(R.color.high_emphasis_text)) binding.dndHeadline.setTextColor(resources.getColor(R.color.high_emphasis_text)) binding.invisibleHeadline.setTextColor(resources.getColor(R.color.high_emphasis_text)) + + binding.onlineIcon.imageTintList = null + binding.awayIcon.imageTintList = null + binding.dndIcon.imageTintList = null + binding.invisibleIcon.imageTintList = null } } private fun setStatusMessage() { - val inputText = binding.customStatusInput.text.toString().ifEmpty { "" } // The endpoint '/message/custom' expects a valid emoji as string or null val statusIcon = binding.emoji.text.toString().ifEmpty { null } @@ -446,7 +448,6 @@ class SetStatusDialogFragment : selectedPredefinedStatus!!.message != inputText || selectedPredefinedStatus!!.icon != binding.emoji.text.toString() ) { - ncApi.setCustomStatusMessage( credentials, ApiUtils.getUrlForSetCustomStatus(currentUser?.baseUrl), @@ -476,12 +477,13 @@ class SetStatusDialogFragment : } }) } else { - val clearAt = clearAtToUnixTime(selectedPredefinedStatus!!.clearAt) ncApi.setPredefinedStatusMessage( - credentials, ApiUtils.getUrlForSetPredefinedStatus(currentUser?.baseUrl), - selectedPredefinedStatus!!.id, if (clearAt == -1L) null else clearAt + credentials, + ApiUtils.getUrlForSetPredefinedStatus(currentUser?.baseUrl), + selectedPredefinedStatus!!.id, + if (clearAt == -1L) null else clearAt ) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread())?.subscribe(object : Observer { @@ -506,7 +508,6 @@ class SetStatusDialogFragment : } override fun onClick(predefinedStatus: PredefinedStatus) { - selectedPredefinedStatus = predefinedStatus clearAt = clearAtToUnixTime(predefinedStatus.clearAt) diff --git a/app/src/main/java/com/nextcloud/talk/ui/dialog/ShowReactionsDialog.kt b/app/src/main/java/com/nextcloud/talk/ui/dialog/ShowReactionsDialog.kt index 602c68cdc..554fc27b7 100644 --- a/app/src/main/java/com/nextcloud/talk/ui/dialog/ShowReactionsDialog.kt +++ b/app/src/main/java/com/nextcloud/talk/ui/dialog/ShowReactionsDialog.kt @@ -51,6 +51,7 @@ import com.nextcloud.talk.models.json.chat.ChatMessage import com.nextcloud.talk.models.json.conversations.Conversation import com.nextcloud.talk.models.json.generic.GenericOverall import com.nextcloud.talk.models.json.reactions.ReactionsOverall +import com.nextcloud.talk.ui.theme.ServerTheme import com.nextcloud.talk.utils.ApiUtils import io.reactivex.Observer import io.reactivex.android.schedulers.AndroidSchedulers @@ -65,7 +66,8 @@ class ShowReactionsDialog( private val chatMessage: ChatMessage, private val user: User?, private val hasChatPermission: Boolean, - private val ncApi: NcApi + private val ncApi: NcApi, + private val serverTheme: ServerTheme ) : BottomSheetDialog(activity), ReactionItemClickListener { private lateinit var binding: DialogMessageReactionsBinding @@ -96,6 +98,7 @@ class ShowReactionsDialog( adapter?.list?.clear() if (chatMessage.reactions != null && chatMessage.reactions!!.isNotEmpty()) { var reactionsTotal = 0 + binding.emojiReactionsTabs.setSelectedTabIndicatorColor(serverTheme.primaryColor) for ((emoji, amount) in chatMessage.reactions!!) { reactionsTotal = reactionsTotal.plus(amount as Int) val tab: TabLayout.Tab = binding.emojiReactionsTabs.newTab() // Create a new Tab names "First Tab" diff --git a/app/src/main/java/com/nextcloud/talk/ui/dialog/SortingOrderDialogFragment.java b/app/src/main/java/com/nextcloud/talk/ui/dialog/SortingOrderDialogFragment.java index f2d5c8d66..e348504cd 100644 --- a/app/src/main/java/com/nextcloud/talk/ui/dialog/SortingOrderDialogFragment.java +++ b/app/src/main/java/com/nextcloud/talk/ui/dialog/SortingOrderDialogFragment.java @@ -33,9 +33,9 @@ import android.widget.TextView; import com.google.android.material.button.MaterialButton; import com.google.android.material.dialog.MaterialAlertDialogBuilder; -import com.nextcloud.talk.R; import com.nextcloud.talk.application.NextcloudTalkApplication; import com.nextcloud.talk.databinding.SortingOrderFragmentBinding; +import com.nextcloud.talk.ui.theme.ViewThemeUtils; import com.nextcloud.talk.utils.FileSortOrder; import com.nextcloud.talk.utils.preferences.AppPreferences; @@ -46,7 +46,6 @@ import javax.inject.Inject; import androidx.annotation.NonNull; import androidx.fragment.app.DialogFragment; import autodagger.AutoInjector; -import kotlin.jvm.JvmField; /** * Dialog to show and choose the sorting order for the file listing. @@ -60,9 +59,11 @@ public class SortingOrderDialogFragment extends DialogFragment implements View.O private static final String KEY_SORT_ORDER = "SORT_ORDER"; @Inject - @JvmField AppPreferences appPreferences; + @Inject + ViewThemeUtils viewThemeUtils; + private SortingOrderFragmentBinding binding; private View dialogView; @@ -119,7 +120,7 @@ public class SortingOrderDialogFragment extends DialogFragment implements View.O * find all relevant UI elements and set their values. */ private void setupDialogElements() { - binding.cancel.setTextColor(getResources().getColor(R.color.colorPrimary)); + viewThemeUtils.colorMaterialButtonText(binding.cancel); taggedViews = new View[12]; taggedViews[0] = binding.sortByNameAscending; @@ -154,18 +155,17 @@ public class SortingOrderDialogFragment extends DialogFragment implements View.O * tints the icon reflecting the actual sorting choice in the apps primary color. */ private void setupActiveOrderSelection() { - final int color = getResources().getColor(R.color.colorPrimary); - Log.i("SortOrder", "currentSortOrderName="+currentSortOrderName); + Log.i("SortOrder", "currentSortOrderName=" + currentSortOrderName); for (View view : taggedViews) { Log.i("SortOrder", ((FileSortOrder) view.getTag()).getName()); if (!((FileSortOrder) view.getTag()).getName().equals(currentSortOrderName)) { continue; } if (view instanceof MaterialButton) { - ((MaterialButton) view).setIconTintResource(R.color.colorPrimary); + viewThemeUtils.colorMaterialButtonText((MaterialButton) view); } if (view instanceof TextView) { - ((TextView) view).setTextColor(color); + viewThemeUtils.colorTextViewElement((TextView) view); ((TextView) view).setTypeface(Typeface.DEFAULT_BOLD); } } diff --git a/app/src/main/java/com/nextcloud/talk/ui/theme/ServerTheme.kt b/app/src/main/java/com/nextcloud/talk/ui/theme/ServerTheme.kt new file mode 100644 index 000000000..ad31f29d9 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/ui/theme/ServerTheme.kt @@ -0,0 +1,53 @@ +/* + * Nextcloud Talk application + * + * @author Álvaro Brey + * Copyright (C) 2022 Álvaro Brey + * Copyright (C) 2022 Nextcloud GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.nextcloud.talk.ui.theme + +import androidx.annotation.ColorInt + +interface ServerTheme { + @get:ColorInt + val primaryColor: Int + + /** + * Default element color + */ + @get:ColorInt + val colorElement: Int + + /** + * Element color for bright backgrounds + */ + @get:ColorInt + val colorElementBright: Int + + /** + * Element color for dark backgrounds + */ + @get:ColorInt + val colorElementDark: Int + + /** + * Text color for elements + */ + @get:ColorInt + val colorText: Int +} diff --git a/app/src/main/java/com/nextcloud/talk/ui/theme/ServerThemeImpl.kt b/app/src/main/java/com/nextcloud/talk/ui/theme/ServerThemeImpl.kt new file mode 100644 index 000000000..d87aae795 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/ui/theme/ServerThemeImpl.kt @@ -0,0 +1,48 @@ +/* + * Nextcloud Talk application + * + * @author Álvaro Brey + * @author Andy Scherzinger + * Copyright (C) 2022 Álvaro Brey + * Copyright (C) 2022 Andy Scherzinger + * Copyright (C) 2022 Nextcloud GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.nextcloud.talk.ui.theme + +import com.nextcloud.talk.R +import com.nextcloud.talk.models.json.capabilities.ThemingCapability +import com.nextcloud.talk.utils.ui.ColorUtil + +internal class ServerThemeImpl(themingCapability: ThemingCapability?, colorUtil: ColorUtil) : + ServerTheme { + + override val primaryColor: Int + override val colorElement: Int + override val colorElementBright: Int + override val colorElementDark: Int + override val colorText: Int + + init { + primaryColor = colorUtil.getNullSafeColorWithFallbackRes(themingCapability?.color, R.color.colorPrimary) + + colorElement = colorUtil.getNullSafeColor(themingCapability?.colorElement, primaryColor) + colorElementBright = colorUtil.getNullSafeColor(themingCapability?.colorElementBright, primaryColor) + colorElementDark = colorUtil.getNullSafeColor(themingCapability?.colorElementDark, primaryColor) + + colorText = colorUtil.getTextColor(themingCapability?.colorText, primaryColor) + } +} diff --git a/app/src/main/java/com/nextcloud/talk/ui/theme/ServerThemeProvider.kt b/app/src/main/java/com/nextcloud/talk/ui/theme/ServerThemeProvider.kt new file mode 100644 index 000000000..3011956e6 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/ui/theme/ServerThemeProvider.kt @@ -0,0 +1,31 @@ +/* + * Nextcloud Talk application + * + * @author Álvaro Brey + * Copyright (C) 2022 Álvaro Brey + * Copyright (C) 2022 Nextcloud GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.nextcloud.talk.ui.theme + +import com.nextcloud.talk.data.user.model.User +import com.nextcloud.talk.models.json.capabilities.Capabilities + +interface ServerThemeProvider { + fun getServerThemeForUser(user: User?): ServerTheme + fun getServerThemeForCapabilities(capabilities: Capabilities?): ServerTheme + fun getServerThemeForCurrentUser(): ServerTheme +} diff --git a/app/src/main/java/com/nextcloud/talk/ui/theme/ServerThemeProviderImpl.kt b/app/src/main/java/com/nextcloud/talk/ui/theme/ServerThemeProviderImpl.kt new file mode 100644 index 000000000..ada276a40 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/ui/theme/ServerThemeProviderImpl.kt @@ -0,0 +1,65 @@ +/* + * Nextcloud Talk application + * + * @author Álvaro Brey + * @author Andy Scherzinger + * Copyright (C) 2022 Álvaro Brey + * Copyright (C) 2022 Andy Scherzinger + * Copyright (C) 2022 Nextcloud GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.nextcloud.talk.ui.theme + +import com.nextcloud.talk.data.user.model.User +import com.nextcloud.talk.models.json.capabilities.Capabilities +import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew +import com.nextcloud.talk.utils.ui.ColorUtil +import java.util.concurrent.ConcurrentHashMap +import javax.inject.Inject + +internal class ServerThemeProviderImpl @Inject constructor( + private val userProvider: CurrentUserProviderNew, + private val colorUtil: ColorUtil +) : ServerThemeProvider { + + private val themeCache: ConcurrentHashMap = ConcurrentHashMap() + + override fun getServerThemeForUser(user: User?): ServerTheme { + val url: String = if (user?.baseUrl != null) { + user.baseUrl!! + } else { + FALLBACK_URL + } + + if (!themeCache.containsKey(url)) { + themeCache[url] = getServerThemeForCapabilities(user?.capabilities) + } + + return themeCache[url]!! + } + + override fun getServerThemeForCurrentUser(): ServerTheme { + return getServerThemeForUser(userProvider.currentUser.blockingGet()) + } + + override fun getServerThemeForCapabilities(capabilities: Capabilities?): ServerTheme { + return ServerThemeImpl(capabilities?.themingCapability, colorUtil) + } + + companion object { + const val FALLBACK_URL = "NULL" + } +} diff --git a/app/src/main/java/com/nextcloud/talk/ui/theme/ThemeModule.kt b/app/src/main/java/com/nextcloud/talk/ui/theme/ThemeModule.kt new file mode 100644 index 000000000..54410e609 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/ui/theme/ThemeModule.kt @@ -0,0 +1,44 @@ +/* + * Nextcloud Talk application + * + * @author Álvaro Brey + * Copyright (C) 2022 Álvaro Brey + * Copyright (C) 2022 Nextcloud GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.nextcloud.talk.ui.theme + +import com.nextcloud.talk.dagger.modules.ContextModule +import com.nextcloud.talk.utils.database.user.UserModule +import dagger.Binds +import dagger.Module +import dagger.Provides +import dagger.Reusable + +@Module(includes = [ContextModule::class, UserModule::class]) +internal abstract class ThemeModule { + + @Binds + @Reusable + abstract fun bindServerThemeProvider(provider: ServerThemeProviderImpl): ServerThemeProvider + + companion object { + @Provides + fun provideCurrentServerTheme(themeProvider: ServerThemeProvider): ServerTheme { + return themeProvider.getServerThemeForCurrentUser() + } + } +} diff --git a/app/src/main/java/com/nextcloud/talk/ui/theme/ViewThemeUtils.kt b/app/src/main/java/com/nextcloud/talk/ui/theme/ViewThemeUtils.kt new file mode 100644 index 000000000..1cc9adaa5 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/ui/theme/ViewThemeUtils.kt @@ -0,0 +1,364 @@ +/* + * Nextcloud Talk application + * + * @author Álvaro Brey + * Copyright (C) 2022 Álvaro Brey + * Copyright (C) 2022 Nextcloud GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.nextcloud.talk.ui.theme + +import android.content.Context +import android.content.res.ColorStateList +import android.graphics.Color +import android.graphics.PorterDuff +import android.graphics.drawable.Drawable +import android.view.View +import android.widget.CheckBox +import android.widget.EditText +import android.widget.ImageView +import android.widget.ProgressBar +import android.widget.RadioButton +import android.widget.SeekBar +import android.widget.TextView +import androidx.annotation.ColorInt +import androidx.appcompat.content.res.AppCompatResources +import androidx.appcompat.widget.SwitchCompat +import androidx.core.content.ContextCompat +import androidx.core.content.res.ResourcesCompat +import androidx.core.view.children +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout +import com.google.android.material.button.MaterialButton +import com.google.android.material.card.MaterialCardView +import com.google.android.material.chip.Chip +import com.google.android.material.floatingactionbutton.FloatingActionButton +import com.google.android.material.progressindicator.LinearProgressIndicator +import com.google.android.material.tabs.TabLayout +import com.google.android.material.textfield.TextInputLayout +import com.nextcloud.talk.R +import com.nextcloud.talk.utils.DrawableUtils +import com.nextcloud.talk.utils.ui.ColorUtil +import com.nextcloud.talk.utils.ui.PlatformThemeUtil.isDarkMode +import com.yarolegovich.mp.MaterialPreferenceCategory +import com.yarolegovich.mp.MaterialSwitchPreference +import javax.inject.Inject + +@Suppress("TooManyFunctions") +class ViewThemeUtils @Inject constructor(private val theme: ServerTheme, private val colorUtil: ColorUtil) { + + /** + * Color for painting elements + */ + fun getElementColor(context: Context): Int = when { + isDarkMode(context) -> theme.colorElementDark + else -> theme.colorElementBright + } + + private fun withElementColor(view: View, block: (Int) -> Unit) { + block(getElementColor(view.context)) + } + + fun themeFAB(fab: FloatingActionButton) { + withElementColor(fab) { color -> + fab.backgroundTintList = ColorStateList.valueOf(color) + fab.imageTintList = ColorStateList.valueOf(theme.colorText) + } + } + + fun themeHorizontalSeekBar(seekBar: SeekBar) { + withElementColor(seekBar) { color -> + themeHorizontalSeekBar(seekBar, color) + } + } + + fun themeHorizontalSeekBar(seekBar: SeekBar, @ColorInt color: Int) { + themeHorizontalProgressBar(seekBar, color) + seekBar.thumb.setColorFilter(color, PorterDuff.Mode.SRC_IN) + } + + fun themeHorizontalProgressBar(progressBar: ProgressBar?, @ColorInt color: Int) { + if (progressBar != null) { + progressBar.indeterminateDrawable.setColorFilter(color, PorterDuff.Mode.SRC_IN) + progressBar.progressDrawable.setColorFilter(color, PorterDuff.Mode.SRC_IN) + } + } + + fun colorTextViewElement(textView: TextView) { + withElementColor(textView) { color -> + textView.setTextColor(color) + } + } + + fun colorTextViewText(textView: TextView) { + textView.setTextColor(theme.colorText) + } + + /** + * Colors the background as element color and the foreground as text color. + */ + fun colorImageViewButton(imageView: ImageView) { + withElementColor(imageView) { color -> + imageView.imageTintList = ColorStateList.valueOf(theme.colorText) + imageView.backgroundTintList = ColorStateList.valueOf(color) + } + } + + /** + * Tints the image with element color + */ + fun colorImageView(imageView: ImageView) { + withElementColor(imageView) { color -> + imageView.imageTintList = ColorStateList.valueOf(color) + } + } + + /** + * Tints the image with text color + */ + fun colorImageViewText(imageView: ImageView) { + imageView.imageTintList = ColorStateList.valueOf(theme.colorText) + } + + fun colorMaterialButtonText(button: MaterialButton) { + withElementColor(button) { color -> + val disabledColor = ContextCompat.getColor(button.context, R.color.disabled_text) + val colorStateList = ColorStateList( + arrayOf( + intArrayOf(android.R.attr.state_enabled), + intArrayOf(-android.R.attr.state_enabled) + ), + intArrayOf(color, disabledColor) + ) + button.setTextColor(colorStateList) + button.iconTint = colorStateList + } + } + + fun colorMaterialButtonBackground(button: MaterialButton) { + withElementColor(button) { color -> + button.setBackgroundColor(color) + + val disabledColor = ContextCompat.getColor(button.context, R.color.disabled_text) + val colorStateList = ColorStateList( + arrayOf( + intArrayOf(android.R.attr.state_enabled), + intArrayOf(-android.R.attr.state_enabled) + ), + intArrayOf(theme.colorText, disabledColor) + ) + + button.setTextColor(colorStateList) + button.iconTint = colorStateList + } + } + + fun colorCardViewBackground(card: MaterialCardView) { + withElementColor(card) { color -> + card.setCardBackgroundColor(color) + } + } + + // TODO split this util into classes depending on framework views vs library views + fun colorPreferenceCategory(category: MaterialPreferenceCategory) { + withElementColor(category) { color -> + category.setTitleColor(color) + } + } + + fun colorSwitchPreference(preference: MaterialSwitchPreference) { + val children = preference.children + val switch = children.find { it is SwitchCompat } + if (switch != null) { + val switchCompat = (switch as SwitchCompat) + colorSwitchCompat(switchCompat) + } + } + + fun colorSwitchCompat(switchCompat: SwitchCompat) { + withElementColor(switchCompat) { color -> + + val context = switchCompat.context + + val thumbUncheckedColor = ResourcesCompat.getColor( + context.resources, + R.color.switch_thumb_color_unchecked, + context.theme + ) + val trackUncheckedColor = ResourcesCompat.getColor( + context.resources, + R.color.switch_track_color_unchecked, + context.theme + ) + + val trackColor = + Color.argb(SWITCHCOMPAT_TRACK_ALPHA, Color.red(color), Color.green(color), Color.blue(color)) + switchCompat.thumbTintList = ColorStateList( + arrayOf(intArrayOf(android.R.attr.state_checked), intArrayOf()), + intArrayOf(color, thumbUncheckedColor) + ) + + switchCompat.trackTintList = ColorStateList( + arrayOf(intArrayOf(android.R.attr.state_checked), intArrayOf()), + intArrayOf(trackColor, trackUncheckedColor) + ) + } + } + + fun colorDrawable(context: Context, drawable: Drawable) { + val color = getElementColor(context) + drawable.setTint(color) + } + + fun themeCheckbox(checkbox: CheckBox) { + withElementColor(checkbox) { color -> + checkbox.buttonTintList = ColorStateList( + arrayOf( + intArrayOf(-android.R.attr.state_checked), + intArrayOf(android.R.attr.state_checked) + ), + intArrayOf(Color.GRAY, color) + ) + } + } + + fun themeRadioButton(radioButton: RadioButton) { + withElementColor(radioButton) { color -> + radioButton.buttonTintList = ColorStateList( + arrayOf( + intArrayOf(-android.R.attr.state_checked), + intArrayOf(android.R.attr.state_checked) + ), + intArrayOf(Color.GRAY, color) + ) + } + } + + fun themeSwipeRefreshLayout(swipeRefreshLayout: SwipeRefreshLayout) { + withElementColor(swipeRefreshLayout) { color -> + swipeRefreshLayout.setColorSchemeColors(color) + swipeRefreshLayout.setProgressBackgroundColorSchemeResource(R.color.refresh_spinner_background) + } + } + + fun colorProgressBar(progressIndicator: LinearProgressIndicator) { + withElementColor(progressIndicator) { color -> + progressIndicator.setIndicatorColor(progressColor(progressIndicator.context, color)) + } + } + + private fun progressColor(context: Context, color: Int): Int { + val lightness = when (isDarkMode(context)) { + true -> PROGRESS_LIGHTNESS_DARK_THEME + false -> PROGRESS_LIGHTNESS_LIGHT_THEME + } + return colorUtil.setLightness(color, lightness) + } + + fun colorEditText(editText: EditText) { + withElementColor(editText) { color -> + editText.setTextColor(color) + // TODO check API-level compatibility + // editText.background.setColorFilter(color, PorterDuff.Mode.SRC_ATOP) + editText.backgroundTintList = ColorStateList( + arrayOf( + intArrayOf(-android.R.attr.state_focused), + intArrayOf(android.R.attr.state_focused) + ), + intArrayOf( + Color.GRAY, + color + ) + ) + } + } + + fun colorTextInputLayout(textInputLayout: TextInputLayout) { + withElementColor(textInputLayout) { color -> + val errorColor = Color.GRAY + + val errorColorStateList = ColorStateList( + arrayOf( + intArrayOf(-android.R.attr.state_focused), + intArrayOf(android.R.attr.state_focused) + ), + intArrayOf( + errorColor, + errorColor + ) + ) + val coloredColorStateList = ColorStateList( + arrayOf( + intArrayOf(-android.R.attr.state_focused), + intArrayOf(android.R.attr.state_focused) + ), + intArrayOf( + Color.GRAY, + color + ) + ) + + textInputLayout.setBoxStrokeColorStateList(coloredColorStateList) + textInputLayout.setErrorIconTintList(errorColorStateList) + textInputLayout.setErrorTextColor(errorColorStateList) + textInputLayout.boxStrokeErrorColor = errorColorStateList + textInputLayout.defaultHintTextColor = coloredColorStateList + } + } + + fun colorTabLayout(tabLayout: TabLayout) { + withElementColor(tabLayout) { color -> + tabLayout.setSelectedTabIndicatorColor(color) + } + } + + fun getPlaceholderImage(context: Context, mimetype: String?): Drawable? { + val drawableResourceId = DrawableUtils.getDrawableResourceIdForMimeType(mimetype) + val drawable = AppCompatResources.getDrawable( + context, + drawableResourceId + ) + if (drawable != null && THEMEABLE_PLACEHOLDER_IDS.contains(drawableResourceId)) { + colorDrawable(context, drawable) + } + return drawable + } + + fun colorChipBackground(chip: Chip) { + withElementColor(chip) { color -> + chip.chipBackgroundColor = ColorStateList.valueOf(color) + chip.setTextColor(theme.colorText) + } + } + + fun colorChipOutlined(chip: Chip, strokeWidth: Float) { + withElementColor(chip) { color -> + chip.chipBackgroundColor = ColorStateList.valueOf(Color.TRANSPARENT) + chip.chipStrokeWidth = strokeWidth + chip.chipStrokeColor = ColorStateList.valueOf(color) + chip.setTextColor(color) + } + } + + companion object { + private val THEMEABLE_PLACEHOLDER_IDS = listOf( + R.drawable.ic_mimetype_package_x_generic, + R.drawable.ic_mimetype_folder + ) + private const val SWITCHCOMPAT_TRACK_ALPHA: Int = 77 + private const val PROGRESS_LIGHTNESS_LIGHT_THEME: Float = 0.76f + private const val PROGRESS_LIGHTNESS_DARK_THEME: Float = 0.28f + } +} diff --git a/app/src/main/java/com/nextcloud/talk/users/UserManager.kt b/app/src/main/java/com/nextcloud/talk/users/UserManager.kt index 06f70b4ee..a61894bfa 100644 --- a/app/src/main/java/com/nextcloud/talk/users/UserManager.kt +++ b/app/src/main/java/com/nextcloud/talk/users/UserManager.kt @@ -28,23 +28,28 @@ import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.models.ExternalSignalingServer import com.nextcloud.talk.models.json.capabilities.Capabilities import com.nextcloud.talk.models.json.push.PushConfigurationState -import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew import io.reactivex.Maybe +import io.reactivex.Observable import io.reactivex.Single @Suppress("TooManyFunctions") -class UserManager internal constructor(private val userRepository: UsersRepository) : CurrentUserProviderNew { +class UserManager internal constructor(private val userRepository: UsersRepository) { val users: Single> get() = userRepository.getUsers() val usersScheduledForDeletion: Single> get() = userRepository.getUsersScheduledForDeletion() - override val currentUser: Maybe + val currentUser: Maybe get() { return userRepository.getActiveUser() } + val currentUserObservable: Observable + get() { + return userRepository.getActiveUserObservable() + } + fun deleteUser(internalId: Long): Int { return userRepository.deleteUser(userRepository.getUserWithId(internalId).blockingGet()) } diff --git a/app/src/main/java/com/nextcloud/talk/utils/DrawableUtils.kt b/app/src/main/java/com/nextcloud/talk/utils/DrawableUtils.kt index ac24b9571..1297c661f 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/DrawableUtils.kt +++ b/app/src/main/java/com/nextcloud/talk/utils/DrawableUtils.kt @@ -148,33 +148,23 @@ object DrawableUtils { drawableMap["unknown"] = R.drawable.ic_mimetype_file drawableMap["application/pdf"] = R.drawable.ic_mimetype_application_pdf - if (localMimetype.isNullOrEmpty()) { - return drawableMap["unknown"]!! - } - - if ("DIR" == localMimetype) { + return if (localMimetype.isNullOrEmpty()) { + drawableMap["unknown"]!! + } else if ("DIR" == localMimetype) { localMimetype = FOLDER - return drawableMap[localMimetype]!! - } - - if (drawableMap.containsKey(localMimetype)) { - return drawableMap[localMimetype]!! - } - - if (localMimetype.startsWith(IMAGE_PREFIX)) { - return R.drawable.ic_mimetype_image - } - - if (localMimetype.startsWith(VIDEO_PREFIX)) { - return R.drawable.ic_mimetype_video - } - - if (localMimetype.startsWith(TEXT_PREFIX)) { - return R.drawable.ic_mimetype_text - } - - return if (localMimetype.startsWith(AUDIO_PREFIX)) { + drawableMap[localMimetype]!! + } else if (drawableMap.containsKey(localMimetype)) { + drawableMap[localMimetype]!! + } else if (localMimetype.startsWith(IMAGE_PREFIX)) { + R.drawable.ic_mimetype_image + } else if (localMimetype.startsWith(VIDEO_PREFIX)) { + R.drawable.ic_mimetype_video + } else if (localMimetype.startsWith(TEXT_PREFIX)) { + R.drawable.ic_mimetype_text + } else if (localMimetype.startsWith(AUDIO_PREFIX)) { R.drawable.ic_mimetype_audio - } else drawableMap["unknown"]!! + } else { + drawableMap["unknown"]!! + } } } diff --git a/app/src/main/java/com/nextcloud/talk/utils/database/user/CurrentUserProviderImpl.kt b/app/src/main/java/com/nextcloud/talk/utils/database/user/CurrentUserProviderImpl.kt new file mode 100644 index 000000000..57848ff47 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/utils/database/user/CurrentUserProviderImpl.kt @@ -0,0 +1,49 @@ +/* + * Nextcloud Talk application + * + * @author Álvaro Brey + * Copyright (C) 2022 Álvaro Brey + * Copyright (C) 2022 Nextcloud GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.nextcloud.talk.utils.database.user + +import com.nextcloud.talk.data.user.model.User +import com.nextcloud.talk.users.UserManager +import io.reactivex.Maybe +import io.reactivex.disposables.Disposable +import javax.inject.Inject + +/** + * Listens to changes in the database and provides the current user without needing to query the database everytime. + */ +class CurrentUserProviderImpl @Inject constructor(private val userManager: UserManager) : CurrentUserProviderNew { + private var _currentUser: User? = null + private var currentUserObserver: Disposable? = null + + override val currentUser: Maybe + get() { + if (_currentUser == null) { + // immediately get a result synchronously + _currentUser = userManager.currentUser.blockingGet() + if (currentUserObserver == null) { + // start observable for auto-updates + currentUserObserver = userManager.currentUserObservable.subscribe { _currentUser = it } + } + } + return _currentUser?.let { Maybe.just(it) } ?: Maybe.empty() + } +} diff --git a/app/src/main/java/com/nextcloud/talk/utils/database/user/UserModule.kt b/app/src/main/java/com/nextcloud/talk/utils/database/user/UserModule.kt index a0453929a..977e36325 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/database/user/UserModule.kt +++ b/app/src/main/java/com/nextcloud/talk/utils/database/user/UserModule.kt @@ -30,7 +30,7 @@ import dagger.Provides abstract class UserModule { @Binds - abstract fun bindCurrentUserProviderNew(userManager: UserManager): CurrentUserProviderNew + abstract fun bindCurrentUserProviderNew(currentUserProviderImpl: CurrentUserProviderImpl): CurrentUserProviderNew companion object { @Provides diff --git a/app/src/main/java/com/nextcloud/talk/utils/ui/ColorUtil.kt b/app/src/main/java/com/nextcloud/talk/utils/ui/ColorUtil.kt new file mode 100644 index 000000000..97d90ab49 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/utils/ui/ColorUtil.kt @@ -0,0 +1,81 @@ +/* + * Nextcloud Talk application + * + * @author Álvaro Brey + * Copyright (C) 2022 Álvaro Brey + * Copyright (C) 2022 Nextcloud GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.nextcloud.talk.utils.ui + +import android.content.Context +import android.graphics.Color +import androidx.annotation.ColorInt +import androidx.annotation.ColorRes +import androidx.core.content.ContextCompat +import androidx.core.graphics.ColorUtils +import com.nextcloud.talk.R +import javax.inject.Inject + +class ColorUtil @Inject constructor(private val context: Context) { + + @ColorInt + fun getNullSafeColor(color: String?, @ColorInt fallbackColor: Int): Int { + return color.parseColorOrFallback { fallbackColor } + } + + @ColorInt + fun getNullSafeColorWithFallbackRes(color: String?, @ColorRes fallbackColorRes: Int): Int { + return color.parseColorOrFallback { ContextCompat.getColor(context, fallbackColorRes) } + } + + @ColorInt + fun getTextColor(colorText: String?, @ColorInt backgroundColor: Int): Int { + return colorText.parseColorOrFallback { getForegroundColorForBackgroundColor(backgroundColor) } + } + + @ColorInt + fun getForegroundColorForBackgroundColor(@ColorInt color: Int): Int { + val hsl = FloatArray(HSL_SIZE) + ColorUtils.RGBToHSL(Color.red(color), Color.green(color), Color.blue(color), hsl) + + return if (hsl[INDEX_LIGHTNESS] < LIGHTNESS_DARK_THRESHOLD) { + Color.WHITE + } else { + ContextCompat.getColor(context, R.color.grey_900) + } + } + + fun setLightness(@ColorInt color: Int, lightness: Float): Int { + require(lightness in 0.0..1.0) { "Lightness must be between 0 and 1" } + val hsl = FloatArray(HSL_SIZE) + ColorUtils.RGBToHSL(Color.red(color), Color.green(color), Color.blue(color), hsl) + + hsl[INDEX_LIGHTNESS] = lightness + + return ColorUtils.HSLToColor(hsl) + } + + @ColorInt + private fun String?.parseColorOrFallback(fallback: () -> Int): Int { + return this?.let { Color.parseColor(this) } ?: fallback() + } + + companion object { + private const val HSL_SIZE: Int = 3 + private const val INDEX_LIGHTNESS: Int = 2 + private const val LIGHTNESS_DARK_THRESHOLD: Float = 0.6f + } +} diff --git a/app/src/main/java/com/nextcloud/talk/utils/ui/PlatformThemeUtil.kt b/app/src/main/java/com/nextcloud/talk/utils/ui/PlatformThemeUtil.kt new file mode 100644 index 000000000..5f65c3d24 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/utils/ui/PlatformThemeUtil.kt @@ -0,0 +1,33 @@ +/* + * Nextcloud Talk application + * + * @author Álvaro Brey + * Copyright (C) 2022 Álvaro Brey + * Copyright (C) 2022 Nextcloud GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.nextcloud.talk.utils.ui + +import android.content.Context +import android.content.res.Configuration + +object PlatformThemeUtil { + fun isDarkMode(context: Context): Boolean = + when (context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) { + Configuration.UI_MODE_NIGHT_YES -> true + else -> false + } +} diff --git a/app/src/main/res/layout/activity_take_picture.xml b/app/src/main/res/layout/activity_take_picture.xml index 8f4d1de80..5382eb3e4 100644 --- a/app/src/main/res/layout/activity_take_picture.xml +++ b/app/src/main/res/layout/activity_take_picture.xml @@ -222,6 +222,7 @@ android:theme="@style/Button.Primary" android:tint="@android:color/white" android:visibility="gone" + tools:visibility="visible" app:backgroundTint="@color/colorPrimary" app:cornerRadius="48dp" app:elevation="0dp" diff --git a/app/src/main/res/layout/controller_conversation_info.xml b/app/src/main/res/layout/controller_conversation_info.xml index 269449006..42944cdd3 100644 --- a/app/src/main/res/layout/controller_conversation_info.xml +++ b/app/src/main/res/layout/controller_conversation_info.xml @@ -23,6 +23,7 @@ @@ -76,6 +77,7 @@ android:layout_width="@dimen/avatar_size_big" android:layout_height="@dimen/avatar_size_big" android:layout_centerHorizontal="true" + tools:background="@color/hwSecurityRed" apc:roundAsCircle="true" /> diff --git a/app/src/main/res/layout/controller_conversations_rv.xml b/app/src/main/res/layout/controller_conversations_rv.xml index c595b9ed8..06aa71d2b 100644 --- a/app/src/main/res/layout/controller_conversations_rv.xml +++ b/app/src/main/res/layout/controller_conversations_rv.xml @@ -115,11 +115,11 @@ android:layout_height="wrap_content" android:layout_gravity="bottom|end" android:layout_margin="16dp" - android:backgroundTint="@color/colorPrimary" android:contentDescription="@string/nc_new_conversation" app:borderWidth="0dp" app:srcCompat="@drawable/ic_add_white_24px" - app:tint="@color/white" /> + app:tint="@color/white" + app:backgroundTint="@color/colorPrimary"/> + android:textColor="@color/high_emphasis_text" /> @@ -78,7 +78,7 @@ android:contentDescription="@string/nc_add_emojis" android:src="@drawable/ic_insert_emoticon_black_24dp" android:visibility="gone" - app:tint="@color/emoji_icons" + app:tint="@color/medium_emphasis_text" tools:visibility="visible" /> diff --git a/app/src/main/res/layout/controller_profile.xml b/app/src/main/res/layout/controller_profile.xml index 21447143d..4d850c72c 100644 --- a/app/src/main/res/layout/controller_profile.xml +++ b/app/src/main/res/layout/controller_profile.xml @@ -29,7 +29,8 @@ + android:layout_height="wrap_content" + android:paddingBottom="@dimen/standard_padding"> @@ -43,6 +44,7 @@ android:id="@+id/message_text" android:layout_width="match_parent" android:layout_height="match_parent" + tools:text="This is a test message" android:gravity="center" /> @@ -105,10 +107,10 @@ android:id="@+id/server_age_warning_text" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_centerHorizontal="true" android:layout_toEndOf="@id/server_age_warning_icon" android:paddingStart="@dimen/standard_padding" android:paddingEnd="0dp" - android:layout_centerHorizontal="true" android:textAlignment="viewStart" android:textColor="@color/nc_darkRed" tools:text="@string/nc_settings_server_almost_eol" /> @@ -142,6 +144,7 @@ diff --git a/app/src/main/res/layout/dialog_poll_create.xml b/app/src/main/res/layout/dialog_poll_create.xml index da0fe5f7f..b03a5c146 100644 --- a/app/src/main/res/layout/dialog_poll_create.xml +++ b/app/src/main/res/layout/dialog_poll_create.xml @@ -35,6 +35,7 @@ android:paddingTop="@dimen/dialog_padding_top_bottom"> + android:contentDescription="@null" + app:layout_alignSelf="center" /> @@ -83,8 +83,8 @@ android:layout_height="wrap_content" android:layout_below="@id/messageText" android:layout_marginStart="8dp" - app:layout_alignSelf="center" android:textColor="@color/nc_outcoming_text_default" + app:layout_alignSelf="center" tools:text="10:35" /> + app:layout_alignSelf="center" /> - #61000000 - #666666 #FFFFFF @@ -82,8 +80,6 @@ #EFEFEF #66EFEFEF - @color/colorPrimary - #800082C9 #FFFFFF #121212 @@ -111,4 +107,7 @@ #FFFFFF #FFFFFF + #ececec + #b2b2b2 + diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 9f4ad1f17..7e63618e5 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -72,6 +72,7 @@ 48dp 40dp 2dp + 12dp 18dp