diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/info/user/ChannelAdapter.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/info/user/ChannelAdapter.kt new file mode 100644 index 0000000000000000000000000000000000000000..42ada913cdeddfd996029801c4ef864f2bd4d06f --- /dev/null +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/info/user/ChannelAdapter.kt @@ -0,0 +1,103 @@ +/* + * Quasseldroid - Quassel client for Android + * + * Copyright (c) 2019 Janne Koschinski + * Copyright (c) 2019 The Quassel Project + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package de.kuschku.quasseldroid.ui.info.user + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.RecyclerView +import butterknife.BindView +import butterknife.ButterKnife +import de.kuschku.libquassel.protocol.NetworkId +import de.kuschku.libquassel.quassel.BufferInfo +import de.kuschku.quasseldroid.R +import de.kuschku.quasseldroid.ui.chat.ChatActivity +import de.kuschku.quasseldroid.util.helper.visibleIf +import de.kuschku.quasseldroid.util.lists.ListAdapter +import de.kuschku.quasseldroid.viewmodel.data.BufferProps + +class ChannelAdapter : ListAdapter<BufferProps, ChannelAdapter.ChannelViewHolder>( + object : DiffUtil.ItemCallback<BufferProps>() { + override fun areItemsTheSame(oldItem: BufferProps, newItem: BufferProps) = + oldItem.info.bufferId == newItem.info.bufferId + + override fun areContentsTheSame(oldItem: BufferProps, newItem: BufferProps) = + oldItem == newItem + } +) { + private var clickListener: ((NetworkId, String) -> Unit)? = null + fun setOnClickListener(listener: ((NetworkId, String) -> Unit)?) { + this.clickListener = listener + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ChannelViewHolder( + LayoutInflater.from(parent.context).inflate( + R.layout.widget_buffer, parent, false + ), + clickListener = clickListener + ) + + override fun onBindViewHolder(holder: ChannelViewHolder, position: Int) = + holder.bind(getItem(position)) + + class ChannelViewHolder( + itemView: View, + private val clickListener: ((NetworkId, String) -> Unit)? = null + ) : RecyclerView.ViewHolder(itemView) { + @BindView(R.id.status) + lateinit var status: ImageView + + @BindView(R.id.name) + lateinit var name: TextView + + @BindView(R.id.description) + lateinit var description: TextView + + var info: BufferInfo? = null + + init { + ButterKnife.bind(this, itemView) + itemView.setOnClickListener { + info?.let { + ChatActivity.launch( + itemView.context, + networkId = it.networkId, + channel = it.bufferName + ) + } + } + } + + fun bind(props: BufferProps) { + info = props.info + + name.text = props.info.bufferName + description.text = props.description + + description.visibleIf(props.description.isNotBlank()) + + status.setImageDrawable(props.fallbackDrawable) + } + } +} + diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/info/user/IrcUserInfo.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/info/user/IrcUserInfo.kt index 98adeadaecc6eb5c0e1b5def9b8f48d535f49fae..32362da4685fa82be0f2532a13ff31c4a0c09d09 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/info/user/IrcUserInfo.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/info/user/IrcUserInfo.kt @@ -23,6 +23,7 @@ import de.kuschku.libquassel.protocol.NetworkId import de.kuschku.libquassel.quassel.BufferInfo import de.kuschku.libquassel.quassel.syncables.IrcUser import de.kuschku.libquassel.quassel.syncables.Network +import de.kuschku.quasseldroid.viewmodel.data.BufferProps data class IrcUserInfo( val networkId: NetworkId, @@ -37,5 +38,6 @@ data class IrcUserInfo( val network: Network? = null, val knownToCore: Boolean = false, val info: BufferInfo? = null, - val ircUser: IrcUser? = null + val ircUser: IrcUser? = null, + val channels: List<BufferProps> = emptyList() ) diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/info/user/UserInfoFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/info/user/UserInfoFragment.kt index 05b9a2bcc09818d820f2ea869c6a7d7669e886a8..1b6ed5b76849fd30a009adc1f107964efb695dc8 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/info/user/UserInfoFragment.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/info/user/UserInfoFragment.kt @@ -31,12 +31,18 @@ import android.widget.Button import android.widget.ImageView import android.widget.TextView import androidx.lifecycle.Observer +import androidx.recyclerview.widget.DefaultItemAnimator +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView import butterknife.BindView import butterknife.ButterKnife import de.kuschku.libquassel.protocol.BufferId import de.kuschku.libquassel.protocol.Buffer_Type +import de.kuschku.libquassel.protocol.Message_Type import de.kuschku.libquassel.protocol.NetworkId import de.kuschku.libquassel.quassel.BufferInfo +import de.kuschku.libquassel.quassel.syncables.BufferSyncer +import de.kuschku.libquassel.quassel.syncables.IrcChannel import de.kuschku.libquassel.quassel.syncables.IrcUser import de.kuschku.libquassel.util.Optional import de.kuschku.libquassel.util.helpers.nullIf @@ -46,6 +52,7 @@ import de.kuschku.quasseldroid.R import de.kuschku.quasseldroid.settings.MessageSettings import de.kuschku.quasseldroid.ui.chat.ChatActivity import de.kuschku.quasseldroid.ui.coresettings.ignorelist.IgnoreListActivity +import de.kuschku.quasseldroid.util.ColorContext import de.kuschku.quasseldroid.util.ShortcutCreationHelper import de.kuschku.quasseldroid.util.avatars.AvatarHelper import de.kuschku.quasseldroid.util.avatars.MatrixApi @@ -57,6 +64,9 @@ import de.kuschku.quasseldroid.util.service.ServiceBoundFragment import de.kuschku.quasseldroid.util.ui.BetterLinkMovementMethod import de.kuschku.quasseldroid.util.ui.LinkLongClickMenuHelper import de.kuschku.quasseldroid.viewmodel.data.Avatar +import de.kuschku.quasseldroid.viewmodel.data.BufferHiddenState +import de.kuschku.quasseldroid.viewmodel.data.BufferProps +import de.kuschku.quasseldroid.viewmodel.data.BufferStatus import io.reactivex.Observable import javax.inject.Inject @@ -115,6 +125,9 @@ class UserInfoFragment : ServiceBoundFragment() { @BindView(R.id.server) lateinit var server: TextView + @BindView(R.id.common_channels) + lateinit var commonChannels: RecyclerView + @Inject lateinit var contentFormatter: ContentFormatter @@ -144,32 +157,83 @@ class UserInfoFragment : ServiceBoundFragment() { actionShortcut.visibleIf(currentBufferInfo != null) } + val commonChannelsAdapter = ChannelAdapter() + commonChannels.layoutManager = LinearLayoutManager(context) + commonChannels.itemAnimator = DefaultItemAnimator() + commonChannels.adapter = commonChannelsAdapter + + val colorContext = ColorContext(requireContext(), messageSettings) + + val colorAccent = requireContext().theme.styledAttributes(R.attr.colorAccent) { + getColor(0, 0) + } + + val colorAway = requireContext().theme.styledAttributes(R.attr.colorAway) { + getColor(0, 0) + } + combineLatest(viewModel.session, viewModel.networks).switchMap { (sessionOptional, networks) -> - fun processUser(user: IrcUser, info: BufferInfo? = null): Optional<IrcUserInfo> { + fun processUser(user: IrcUser, bufferSyncer: BufferSyncer? = null, + info: BufferInfo? = null): Observable<Optional<IrcUserInfo>> { actionShortcut.post(::updateShortcutVisibility) return when { - user == IrcUser.NULL && info != null -> Optional.of(IrcUserInfo( + user == IrcUser.NULL && info != null -> Observable.just(Optional.of(IrcUserInfo( networkId = info.networkId, nick = info.bufferName ?: "", knownToCore = true, info = info - )) - user == IrcUser.NULL -> Optional.empty() - else -> Optional.of(IrcUserInfo( - networkId = user.network().networkId(), - nick = user.nick(), - user = user.user(), - host = user.host(), - account = user.account(), - server = user.server(), - realName = user.realName(), - isAway = user.isAway(), - awayMessage = user.awayMessage(), - network = user.network(), - knownToCore = true, - info = info, - ircUser = user - )) + ))) + user == IrcUser.NULL -> Observable.just(Optional.empty()) + else -> { + combineLatest(user.channels().map { channelName -> + user.network().liveIrcChannel( + channelName + ).switchMap { channel -> + channel.updates().map { + bufferSyncer?.find( + bufferName = channelName, + networkId = user.network().networkId() + )?.let { info -> + val bufferStatus = + if (it == IrcChannel.NULL) BufferStatus.OFFLINE + else BufferStatus.ONLINE + val color = + if (bufferStatus == BufferStatus.ONLINE) colorAccent + else colorAway + val fallbackDrawable = colorContext.buildTextDrawable("#", color) + + BufferProps( + info = info, + network = user.network().networkInfo(), + description = it.topic(), + activity = Message_Type.of(), + bufferStatus = bufferStatus, + hiddenState = BufferHiddenState.VISIBLE, + networkConnectionState = user.network().connectionState(), + fallbackDrawable = fallbackDrawable + ) + } + } + } + }).map { + Optional.of(IrcUserInfo( + networkId = user.network().networkId(), + nick = user.nick(), + user = user.user(), + host = user.host(), + account = user.account(), + server = user.server(), + realName = user.realName(), + isAway = user.isAway(), + awayMessage = user.awayMessage(), + network = user.network(), + knownToCore = true, + info = info, + ircUser = user, + channels = it.filterNotNull() + )) + } + } } } @@ -178,16 +242,16 @@ class UserInfoFragment : ServiceBoundFragment() { val bufferSyncer = session?.bufferSyncer val bufferInfo = bufferSyncer?.bufferInfo(bufferId) bufferInfo?.let { - networks[it.networkId]?.liveIrcUser(it.bufferName)?.switchMap(IrcUser::updates)?.map { - processUser(it, bufferInfo) + networks[it.networkId]?.liveIrcUser(it.bufferName)?.switchMap(IrcUser::updates)?.switchMap { + processUser(it, bufferSyncer, bufferInfo) } } } else { networks[networkId] ?.liveIrcUser(nickName) ?.switchMap(IrcUser::updates) - ?.map { user -> processUser(user) } - } ?: Observable.just(IrcUser.NULL).map { user -> processUser(user) } + ?.switchMap { user -> processUser(user, sessionOptional?.orNull()?.bufferSyncer) } + } ?: Observable.just(IrcUser.NULL).switchMap { user -> processUser(user, null, null) } }.toLiveData().observe(this, Observer { val user = it.orNull() if (user != null) { @@ -322,6 +386,8 @@ class UserInfoFragment : ServiceBoundFragment() { } } } + + commonChannelsAdapter.submitList(user.channels) } }) diff --git a/app/src/main/res/layout/info_user.xml b/app/src/main/res/layout/info_user.xml index b33d185d3eb454ae40e2720826235e40d33c3bcc..3025db19a4a4a6e76828f0195c5aabeca517917c 100644 --- a/app/src/main/res/layout/info_user.xml +++ b/app/src/main/res/layout/info_user.xml @@ -251,6 +251,15 @@ style="@style/Widget.Info.Item.Description" android:text="@string/label_user_server" /> </LinearLayout> + + <TextView + style="@style/Widget.Info.Section" + android:text="@string/label_user_common_channels" /> + + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/common_channels" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> </LinearLayout> </androidx.core.widget.NestedScrollView> </androidx.coordinatorlayout.widget.CoordinatorLayout> diff --git a/app/src/main/res/values/strings_info.xml b/app/src/main/res/values/strings_info.xml index 769aa50471cf87e2d1e294e73aa90fbd2c87549a..e0e2fff6b873b8ba7d12d9466036de9f8ca2b8c5 100644 --- a/app/src/main/res/values/strings_info.xml +++ b/app/src/main/res/values/strings_info.xml @@ -25,6 +25,7 @@ <string name="label_user_ident">Ident</string> <string name="label_user_host">Host</string> <string name="label_user_server">Server</string> + <string name="label_user_common_channels">Common Channels</string> <string name="label_core_version">Version</string> <string name="label_core_uptime">Uptime</string>